Promisify tests

GitOrigin-RevId: 6f413f4c5ef8d034b4e94afacdf2d7b43c3a8830
This commit is contained in:
Andrew Rumble
2025-04-24 09:18:42 +01:00
committed by Copybot
parent 30c7a81361
commit 34ae228d99
25 changed files with 3676 additions and 4164 deletions

View File

@@ -1283,6 +1283,7 @@ const ProjectEntityUpdateHandler = {
upsertFile,
upsertFileWithPath,
appendToDocWithPath: appendToDoc,
setMainBibliographyDoc,
},
async _addDocAndSendToTpds(projectId, folderId, doc, userId) {

View File

@@ -206,7 +206,7 @@ describe('AuthorizationMiddleware', function () {
expectForbidden()
})
describe('for a regular user', function (done) {
describe('for a regular user', function () {
setupPermission('isRestrictedUserForProject', false)
invokeMiddleware('blockRestrictedUserFromProject')
expectNext()

View File

@@ -59,47 +59,34 @@ describe('BrandVariationsHandler', function () {
})
describe('getBrandVariationById', function () {
it('should call the callback with an error when the branding variation id is not provided', function (done) {
return this.BrandVariationsHandler.getBrandVariationById(
null,
(err, brandVariationDetails) => {
expect(err).to.be.instanceof(Error)
return done()
}
)
it('should reject with an error when the branding variation id is not provided', async function () {
await expect(
this.BrandVariationsHandler.promises.getBrandVariationById(null)
).to.be.rejected
})
it('should call the callback with an error when the request errors', function (done) {
it('should reject with an error when the request errors', async function () {
this.V1Api.request.callsArgWith(1, new Error())
return this.BrandVariationsHandler.getBrandVariationById(
'12',
(err, brandVariationDetails) => {
expect(err).to.be.instanceof(Error)
return done()
}
)
await expect(
this.BrandVariationsHandler.promises.getBrandVariationById('12')
).to.be.rejected
})
it('should call the callback with branding details when request succeeds', function (done) {
it('should return branding details when request succeeds', async function () {
this.V1Api.request.callsArgWith(
1,
null,
{ statusCode: 200 },
this.mockedBrandVariationDetails
)
return this.BrandVariationsHandler.getBrandVariationById(
'12',
(err, brandVariationDetails) => {
expect(err).to.not.exist
expect(brandVariationDetails).to.deep.equal(
this.mockedBrandVariationDetails
)
return done()
}
const brandVariationDetails =
await this.BrandVariationsHandler.promises.getBrandVariationById('12')
expect(brandVariationDetails).to.deep.equal(
this.mockedBrandVariationDetails
)
})
it('should transform relative URLs in v1 absolute ones', function (done) {
it('should transform relative URLs in v1 absolute ones', async function () {
this.mockedBrandVariationDetails.logo_url = '/journal-logo.png'
this.V1Api.request.callsArgWith(
1,
@@ -107,20 +94,16 @@ describe('BrandVariationsHandler', function () {
{ statusCode: 200 },
this.mockedBrandVariationDetails
)
return this.BrandVariationsHandler.getBrandVariationById(
'12',
(err, brandVariationDetails) => {
expect(
brandVariationDetails.logo_url.startsWith(
this.settings.apis.v1.publicUrl
)
).to.be.true
return done()
}
)
const brandVariationDetails =
await this.BrandVariationsHandler.promises.getBrandVariationById('12')
expect(
brandVariationDetails.logo_url.startsWith(
this.settings.apis.v1.publicUrl
)
).to.be.true
})
it("should sanitize 'submit_button_html'", function (done) {
it("should sanitize 'submit_button_html'", async function () {
this.mockedBrandVariationDetails.submit_button_html =
'<br class="break"/><strong style="color:#B39500">AGU Journal</strong><iframe>hello</iframe>'
this.V1Api.request.callsArgWith(
@@ -129,14 +112,10 @@ describe('BrandVariationsHandler', function () {
{ statusCode: 200 },
this.mockedBrandVariationDetails
)
return this.BrandVariationsHandler.getBrandVariationById(
'12',
(err, brandVariationDetails) => {
expect(brandVariationDetails.submit_button_html).to.equal(
'<br /><strong style="color:#B39500">AGU Journal</strong>hello'
)
return done()
}
const brandVariationDetails =
await this.BrandVariationsHandler.promises.getBrandVariationById('12')
expect(brandVariationDetails.submit_button_html).to.equal(
'<br /><strong style="color:#B39500">AGU Journal</strong>hello'
)
})
})

View File

@@ -47,8 +47,8 @@ describe('ChatManager', function () {
}))
})
it('should inject a user object into messaged and resolved data', function (done) {
return this.ChatManager.injectUserInfoIntoThreads(
it('should inject a user object into messaged and resolved data', async function () {
const threads = await this.ChatManager.promises.injectUserInfoIntoThreads(
{
thread1: {
resolved: true,
@@ -72,64 +72,56 @@ describe('ChatManager', function () {
},
],
},
},
(error, threads) => {
expect(error).to.be.null
expect(threads).to.deep.equal({
thread1: {
resolved: true,
resolved_by_user_id: 'user_id_1',
resolved_by_user: { formatted: 'user_1' },
messages: [
{
user_id: 'user_id_1',
user: { formatted: 'user_1' },
content: 'foo',
},
{
user_id: 'user_id_2',
user: { formatted: 'user_2' },
content: 'bar',
},
],
},
thread2: {
messages: [
{
user_id: 'user_id_1',
user: { formatted: 'user_1' },
content: 'baz',
},
],
},
})
return done()
}
)
expect(threads).to.deep.equal({
thread1: {
resolved: true,
resolved_by_user_id: 'user_id_1',
resolved_by_user: { formatted: 'user_1' },
messages: [
{
user_id: 'user_id_1',
user: { formatted: 'user_1' },
content: 'foo',
},
{
user_id: 'user_id_2',
user: { formatted: 'user_2' },
content: 'bar',
},
],
},
thread2: {
messages: [
{
user_id: 'user_id_1',
user: { formatted: 'user_1' },
content: 'baz',
},
],
},
})
})
it('should only need to look up each user once', function (done) {
return this.ChatManager.injectUserInfoIntoThreads(
[
{
messages: [
{
user_id: 'user_id_1',
content: 'foo',
},
{
user_id: 'user_id_1',
content: 'bar',
},
],
},
],
(error, threads) => {
expect(error).to.be.null
this.UserInfoManager.getPersonalInfo.calledOnce.should.equal(true)
return done()
}
)
it('should only need to look up each user once', async function () {
await this.ChatManager.promises.injectUserInfoIntoThreads([
{
messages: [
{
user_id: 'user_id_1',
content: 'foo',
},
{
user_id: 'user_id_1',
content: 'bar',
},
],
},
])
this.UserInfoManager.getPersonalInfo.calledOnce.should.equal(true)
})
})
})

View File

@@ -47,62 +47,53 @@ describe('ClsiFormatChecker', function () {
])
})
it('should call _checkDocsAreUnderSizeLimit and _checkForConflictingPaths', function (done) {
it('should call _checkDocsAreUnderSizeLimit and _checkForConflictingPaths', async function () {
this.ClsiFormatChecker._checkForConflictingPaths = sinon
.stub()
.callsArgWith(1, null)
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
.stub()
.callsArgWith(1)
return this.ClsiFormatChecker.checkRecoursesForProblems(
this.resources,
(err, problems) => {
this.ClsiFormatChecker._checkForConflictingPaths.called.should.equal(
true
)
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit.called.should.equal(
true
)
return done()
}
const problems =
await this.ClsiFormatChecker.promises.checkRecoursesForProblems(
this.resources
)
this.ClsiFormatChecker._checkForConflictingPaths.called.should.equal(true)
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit.called.should.equal(
true
)
})
it('should remove undefined errors', function (done) {
it('should remove undefined errors', async function () {
this.ClsiFormatChecker._checkForConflictingPaths = sinon
.stub()
.callsArgWith(1, null, [])
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
.stub()
.callsArgWith(1, null, {})
return this.ClsiFormatChecker.checkRecoursesForProblems(
this.resources,
(err, problems) => {
expect(problems).to.not.exist
expect(problems).to.not.exist
return done()
}
)
const problems =
await this.ClsiFormatChecker.promises.checkRecoursesForProblems(
this.resources
)
expect(problems).to.not.exist
})
it('should keep populated arrays', function (done) {
it('should keep populated arrays', async function () {
this.ClsiFormatChecker._checkForConflictingPaths = sinon
.stub()
.callsArgWith(1, null, [{ path: 'somewhere/main.tex' }])
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
.stub()
.callsArgWith(1, null, {})
return this.ClsiFormatChecker.checkRecoursesForProblems(
this.resources,
(err, problems) => {
problems.conflictedPaths[0].path.should.equal('somewhere/main.tex')
expect(problems.sizeCheck).to.not.exist
return done()
}
)
const problems =
await this.ClsiFormatChecker.promises.checkRecoursesForProblems(
this.resources
)
problems.conflictedPaths[0].path.should.equal('somewhere/main.tex')
expect(problems.sizeCheck).to.not.exist
})
it('should keep populated object', function (done) {
it('should keep populated object', async function () {
this.ClsiFormatChecker._checkForConflictingPaths = sinon
.stub()
.callsArgWith(1, null, [])
@@ -112,15 +103,13 @@ describe('ClsiFormatChecker', function () {
resources: [{ 'a.tex': 'a.tex' }, { 'b.tex': 'b.tex' }],
totalSize: 1000000,
})
return this.ClsiFormatChecker.checkRecoursesForProblems(
this.resources,
(err, problems) => {
problems.sizeCheck.resources.length.should.equal(2)
problems.sizeCheck.totalSize.should.equal(1000000)
expect(problems.conflictedPaths).to.not.exist
return done()
}
)
const problems =
await this.ClsiFormatChecker.promises.checkRecoursesForProblems(
this.resources
)
problems.sizeCheck.resources.length.should.equal(2)
problems.sizeCheck.totalSize.should.equal(1000000)
expect(problems.conflictedPaths).to.not.exist
})
describe('_checkForConflictingPaths', function () {
@@ -136,56 +125,50 @@ describe('ClsiFormatChecker', function () {
})
})
it('should flag up when a nested file has folder with same subpath as file elsewhere', function (done) {
it('should flag up when a nested file has folder with same subpath as file elsewhere', async function () {
this.resources.push({
path: 'stuff/image',
url: 'http://somwhere.com',
})
return this.ClsiFormatChecker._checkForConflictingPaths(
this.resources,
(err, conflictPathErrors) => {
conflictPathErrors.length.should.equal(1)
conflictPathErrors[0].path.should.equal('stuff/image')
return done()
}
)
const conflictPathErrors =
await this.ClsiFormatChecker.promises._checkForConflictingPaths(
this.resources
)
conflictPathErrors.length.should.equal(1)
conflictPathErrors[0].path.should.equal('stuff/image')
})
it('should flag up when a root level file has folder with same subpath as file elsewhere', function (done) {
it('should flag up when a root level file has folder with same subpath as file elsewhere', async function () {
this.resources.push({
path: 'stuff',
content: 'other stuff',
})
return this.ClsiFormatChecker._checkForConflictingPaths(
this.resources,
(err, conflictPathErrors) => {
conflictPathErrors.length.should.equal(1)
conflictPathErrors[0].path.should.equal('stuff')
return done()
}
)
const conflictPathErrors =
await this.ClsiFormatChecker.promises._checkForConflictingPaths(
this.resources
)
conflictPathErrors.length.should.equal(1)
conflictPathErrors[0].path.should.equal('stuff')
})
it('should not flag up when the file is a substring of a path', function (done) {
it('should not flag up when the file is a substring of a path', async function () {
this.resources.push({
path: 'stuf',
content: 'other stuff',
})
return this.ClsiFormatChecker._checkForConflictingPaths(
this.resources,
(err, conflictPathErrors) => {
conflictPathErrors.length.should.equal(0)
return done()
}
)
const conflictPathErrors =
await this.ClsiFormatChecker.promises._checkForConflictingPaths(
this.resources
)
conflictPathErrors.length.should.equal(0)
})
})
describe('_checkDocsAreUnderSizeLimit', function () {
it('should error when there is more than 5mb of data', function (done) {
it('should error when there is more than 5mb of data', async function () {
this.resources.push({
path: 'massive.tex',
content: 'hello world'.repeat(833333), // over 5mb limit
@@ -198,19 +181,17 @@ describe('ClsiFormatChecker', function () {
})
}
return this.ClsiFormatChecker._checkDocsAreUnderSizeLimit(
this.resources,
(err, sizeError) => {
sizeError.totalSize.should.equal(16 + 833333 * 11) // 16 is for earlier resources
sizeError.resources.length.should.equal(10)
sizeError.resources[0].path.should.equal('massive.tex')
sizeError.resources[0].size.should.equal(833333 * 11)
return done()
}
)
const sizeError =
await this.ClsiFormatChecker.promises._checkDocsAreUnderSizeLimit(
this.resources
)
sizeError.totalSize.should.equal(16 + 833333 * 11) // 16 is for earlier resources
sizeError.resources.length.should.equal(10)
sizeError.resources[0].path.should.equal('massive.tex')
sizeError.resources[0].size.should.equal(833333 * 11)
})
it('should return nothing when project is correct size', function (done) {
it('should return nothing when project is correct size', async function () {
this.resources.push({
path: 'massive.tex',
content: 'x'.repeat(2 * 1000 * 1000),
@@ -223,13 +204,11 @@ describe('ClsiFormatChecker', function () {
})
}
return this.ClsiFormatChecker._checkDocsAreUnderSizeLimit(
this.resources,
(err, sizeError) => {
expect(sizeError).to.not.exist
return done()
}
)
const sizeError =
await this.ClsiFormatChecker.promises._checkDocsAreUnderSizeLimit(
this.resources
)
expect(sizeError).to.not.exist
})
})
})

View File

@@ -139,7 +139,7 @@ describe('CompileManager', function () {
})
describe('when the project has been recently compiled', function () {
it('should return', function (done) {
it('should return', async function () {
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
isAutoCompile,
compileGroup
@@ -147,32 +147,27 @@ describe('CompileManager', function () {
this.CompileManager._checkIfRecentlyCompiled = sinon
.stub()
.resolves(true)
this.CompileManager.promises
.compile(this.project_id, this.user_id, {})
.then(({ status }) => {
status.should.equal('too-recently-compiled')
done()
})
.catch(error => {
// Catch any errors and fail the test
true.should.equal(false)
done(error)
})
const { status } = await this.CompileManager.promises.compile(
this.project_id,
this.user_id,
{}
)
status.should.equal('too-recently-compiled')
})
})
describe('should check the rate limit', function () {
it('should return', function (done) {
it('should return', async function () {
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
.stub()
.resolves(false)
this.CompileManager.promises
.compile(this.project_id, this.user_id, {})
.then(({ status }) => {
expect(status).to.equal('autocompile-backoff')
done()
})
.catch(err => done(err))
const { status } = await this.CompileManager.promises.compile(
this.project_id,
this.user_id,
{}
)
expect(status).to.equal('autocompile-backoff')
})
})
})
@@ -250,15 +245,12 @@ describe('CompileManager', function () {
beforeEach(function () {
this.features.compileGroup = 'priority'
})
it('should return the default class', function (done) {
this.CompileManager.getProjectCompileLimits(
this.project_id,
(err, { compileBackendClass }) => {
if (err) return done(err)
expect(compileBackendClass).to.equal('c2d')
done()
}
)
it('should return the default class', async function () {
const { compileBackendClass } =
await this.CompileManager.promises.getProjectCompileLimits(
this.project_id
)
expect(compileBackendClass).to.equal('c2d')
})
})
})

View File

@@ -1,15 +1,3 @@
/* eslint-disable
n/handle-callback-err,
max-len,
no-return-assign,
*/
// 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 { expect } = require('chai')
@@ -23,158 +11,143 @@ describe('CooldownManager', function () {
this.projectId = 'abcdefg'
this.rclient = { set: sinon.stub(), get: sinon.stub() }
this.RedisWrapper = { client: () => this.rclient }
return (this.CooldownManager = SandboxedModule.require(modulePath, {
this.CooldownManager = SandboxedModule.require(modulePath, {
requires: {
'../../infrastructure/RedisWrapper': this.RedisWrapper,
},
}))
})
})
describe('_buildKey', function () {
it('should build a properly formatted redis key', function () {
return expect(this.CooldownManager._buildKey('ABC')).to.equal(
'Cooldown:{ABC}'
)
expect(this.CooldownManager._buildKey('ABC')).to.equal('Cooldown:{ABC}')
})
})
describe('isProjectOnCooldown', function () {
beforeEach(function () {
return (this.call = cb => {
return this.CooldownManager.isProjectOnCooldown(this.projectId, cb)
})
})
describe('when project is on cooldown', function () {
beforeEach(function () {
return (this.rclient.get = sinon.stub().callsArgWith(1, null, '1'))
this.rclient.get = sinon.stub().callsArgWith(1, null, '1')
})
it('should fetch key from redis', function (done) {
return this.call((err, result) => {
this.rclient.get.callCount.should.equal(1)
this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
return done()
})
it('should fetch key from redis', async function () {
await this.CooldownManager.promises.isProjectOnCooldown(this.projectId)
this.rclient.get.callCount.should.equal(1)
this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
})
it('should not produce an error', function (done) {
return this.call((err, result) => {
expect(err).to.equal(null)
return done()
})
})
it('should produce a true result', function (done) {
return this.call((err, result) => {
expect(result).to.equal(true)
return done()
})
it('should produce a true result', async function () {
const result = await this.CooldownManager.promises.isProjectOnCooldown(
this.projectId
)
expect(result).to.equal(true)
})
})
describe('when project is not on cooldown', function () {
beforeEach(function () {
return (this.rclient.get = sinon.stub().callsArgWith(1, null, null))
this.rclient.get = sinon.stub().callsArgWith(1, null, null)
})
it('should fetch key from redis', function (done) {
return this.call((err, result) => {
this.rclient.get.callCount.should.equal(1)
this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
return done()
})
it('should fetch key from redis', async function () {
await this.CooldownManager.promises.isProjectOnCooldown(this.projectId)
this.rclient.get.callCount.should.equal(1)
this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
})
it('should not produce an error', function (done) {
return this.call((err, result) => {
expect(err).to.equal(null)
return done()
})
})
it('should produce a false result', function (done) {
return this.call((err, result) => {
expect(result).to.equal(false)
return done()
})
it('should produce a false result', async function () {
const result = await this.CooldownManager.promises.isProjectOnCooldown(
this.projectId
)
expect(result).to.equal(false)
})
})
describe('when rclient.get produces an error', function () {
beforeEach(function () {
return (this.rclient.get = sinon
.stub()
.callsArgWith(1, new Error('woops')))
this.rclient.get = sinon.stub().callsArgWith(1, new Error('woops'))
})
it('should fetch key from redis', function (done) {
return this.call((err, result) => {
this.rclient.get.callCount.should.equal(1)
this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
return done()
})
it('should fetch key from redis', async function () {
try {
await this.CooldownManager.promises.isProjectOnCooldown(
this.projectId
)
} catch {
// ignore errors - expected
}
this.rclient.get.callCount.should.equal(1)
this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
})
it('should produce an error', function (done) {
return this.call((err, result) => {
expect(err).to.not.equal(null)
expect(err).to.be.instanceof(Error)
return done()
})
it('should produce an error', async function () {
let error
try {
await this.CooldownManager.promises.isProjectOnCooldown(
this.projectId
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
})
})
})
describe('putProjectOnCooldown', function () {
beforeEach(function () {
return (this.call = cb => {
return this.CooldownManager.putProjectOnCooldown(this.projectId, cb)
})
})
describe('when rclient.set does not produce an error', function () {
beforeEach(function () {
return (this.rclient.set = sinon.stub().callsArgWith(4, null))
this.rclient.set = sinon.stub().callsArgWith(4, null)
})
it('should set a key in redis', function (done) {
return this.call(err => {
this.rclient.set.callCount.should.equal(1)
this.rclient.set.calledWith('Cooldown:{abcdefg}').should.equal(true)
return done()
})
it('should set a key in redis', async function () {
await this.CooldownManager.promises.putProjectOnCooldown(this.projectId)
this.rclient.set.callCount.should.equal(1)
this.rclient.set.calledWith('Cooldown:{abcdefg}').should.equal(true)
})
it('should not produce an error', function (done) {
return this.call(err => {
expect(err).to.equal(null)
return done()
})
it('should not produce an error', async function () {
let error
try {
await this.CooldownManager.promises.putProjectOnCooldown(
this.projectId
)
} catch (err) {
error = err
}
expect(error).not.to.exist
})
})
describe('when rclient.set produces an error', function () {
beforeEach(function () {
return (this.rclient.set = sinon
.stub()
.callsArgWith(4, new Error('woops')))
this.rclient.set = sinon.stub().callsArgWith(4, new Error('woops'))
})
it('should set a key in redis', function (done) {
return this.call(err => {
this.rclient.set.callCount.should.equal(1)
this.rclient.set.calledWith('Cooldown:{abcdefg}').should.equal(true)
return done()
})
it('should set a key in redis', async function () {
try {
await this.CooldownManager.promises.putProjectOnCooldown(
this.projectId
)
} catch {
// ignore errors - expected
}
this.rclient.set.callCount.should.equal(1)
this.rclient.set.calledWith('Cooldown:{abcdefg}').should.equal(true)
})
it('produce an error', function (done) {
return this.call(err => {
expect(err).to.not.equal(null)
expect(err).to.be.instanceof(Error)
return done()
})
it('produce an error', async function () {
let error
try {
await this.CooldownManager.promises.putProjectOnCooldown(
this.projectId
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
})
})
})

View File

@@ -1,6 +1,7 @@
const sinon = require('sinon')
const modulePath = '../../../../app/src/Features/Docstore/DocstoreManager'
const SandboxedModule = require('sandboxed-module')
const { expect } = require('chai')
const Errors = require('../../../../app/src/Features/Errors/Errors')
const tk = require('timekeeper')
@@ -26,7 +27,6 @@ describe('DocstoreManager', function () {
this.project_id = 'project-id-123'
this.doc_id = 'doc-id-123'
this.callback = sinon.stub()
})
describe('deleteDoc', function () {
@@ -39,16 +39,15 @@ describe('DocstoreManager', function () {
tk.reset()
})
beforeEach(function () {
beforeEach(async function () {
this.request.patch = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 }, '')
this.DocstoreManager.deleteDoc(
await this.DocstoreManager.promises.deleteDoc(
this.project_id,
this.doc_id,
'wombat.tex',
new Date(),
this.callback
new Date()
)
})
@@ -61,10 +60,6 @@ describe('DocstoreManager', function () {
})
.should.equal(true)
})
it('should call the callback without an error', function () {
this.callback.calledWith(null).should.equal(true)
})
})
describe('with a failed response code', function () {
@@ -72,28 +67,27 @@ describe('DocstoreManager', function () {
this.request.patch = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
this.DocstoreManager.deleteDoc(
this.project_id,
this.doc_id,
'main.tex',
new Date(),
this.callback
)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'docstore api responded with non-success code: 500'
)
)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.deleteDoc(
this.project_id,
this.doc_id,
'main.tex',
new Date()
)
.should.equal(true)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'docstore api responded with non-success code: 500'
)
})
})
@@ -102,28 +96,26 @@ describe('DocstoreManager', function () {
this.request.patch = sinon
.stub()
.callsArgWith(1, null, { statusCode: 404 }, '')
this.DocstoreManager.deleteDoc(
this.project_id,
this.doc_id,
'main.tex',
new Date(),
this.callback
)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Errors.NotFoundError)
.and(
sinon.match.has(
'message',
'tried to delete doc not in docstore'
)
)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.deleteDoc(
this.project_id,
this.doc_id,
'main.tex',
new Date()
)
.should.equal(true)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'tried to delete doc not in docstore'
)
})
})
})
@@ -137,8 +129,8 @@ describe('DocstoreManager', function () {
this.modified = true
})
describe('with a successful response code', function () {
beforeEach(function () {
describe('with a successful response code', async function () {
beforeEach(async function () {
this.request.post = sinon
.stub()
.callsArgWith(
@@ -147,13 +139,12 @@ describe('DocstoreManager', function () {
{ statusCode: 204 },
{ modified: this.modified, rev: this.rev }
)
this.DocstoreManager.updateDoc(
this.updateDocResponse = await this.DocstoreManager.promises.updateDoc(
this.project_id,
this.doc_id,
this.lines,
this.version,
this.ranges,
this.callback
this.ranges
)
})
@@ -171,10 +162,12 @@ describe('DocstoreManager', function () {
.should.equal(true)
})
it('should call the callback with the modified status and revision', function () {
this.callback
.calledWith(null, this.modified, this.rev)
.should.equal(true)
it('should return the modified status and revision', function () {
expect(this.updateDocResponse).to.haveOwnProperty(
'modified',
this.modified
)
expect(this.updateDocResponse).to.haveOwnProperty('rev', this.rev)
})
})
@@ -183,29 +176,28 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
this.DocstoreManager.updateDoc(
this.project_id,
this.doc_id,
this.lines,
this.version,
this.ranges,
this.callback
)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'docstore api responded with non-success code: 500'
)
)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.updateDoc(
this.project_id,
this.doc_id,
this.lines,
this.version,
this.ranges
)
.should.equal(true)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'docstore api responded with non-success code: 500'
)
})
})
})
@@ -221,11 +213,14 @@ describe('DocstoreManager', function () {
})
describe('with a successful response code', function () {
beforeEach(function () {
beforeEach(async function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 }, this.doc)
this.DocstoreManager.getDoc(this.project_id, this.doc_id, this.callback)
this.getDocResponse = await this.DocstoreManager.promises.getDoc(
this.project_id,
this.doc_id
)
})
it('should get the doc from the docstore api', function () {
@@ -236,10 +231,13 @@ describe('DocstoreManager', function () {
})
})
it('should call the callback with the lines, version and rev', function () {
this.callback
.calledWith(null, this.lines, this.rev, this.version, this.ranges)
.should.equal(true)
it('should resolve with the lines, version and rev', function () {
expect(this.getDocResponse).to.eql({
lines: this.lines,
rev: this.rev,
version: this.version,
ranges: this.ranges,
})
})
})
@@ -248,35 +246,37 @@ describe('DocstoreManager', function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
this.DocstoreManager.getDoc(this.project_id, this.doc_id, this.callback)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'docstore api responded with non-success code: 500'
)
)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.getDoc(
this.project_id,
this.doc_id
)
.should.equal(true)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'docstore api responded with non-success code: 500'
)
})
})
describe('with include_deleted=true', function () {
beforeEach(function () {
beforeEach(async function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 }, this.doc)
this.DocstoreManager.getDoc(
this.getDocResponse = await this.DocstoreManager.promises.getDoc(
this.project_id,
this.doc_id,
{ include_deleted: true },
this.callback
{ include_deleted: true }
)
})
@@ -289,23 +289,27 @@ describe('DocstoreManager', function () {
})
})
it('should call the callback with the lines, version and rev', function () {
this.callback
.calledWith(null, this.lines, this.rev, this.version, this.ranges)
.should.equal(true)
it('should resolve with the lines, version and rev', function () {
expect(this.getDocResponse).to.eql({
lines: this.lines,
rev: this.rev,
version: this.version,
ranges: this.ranges,
})
})
})
describe('with peek=true', function () {
beforeEach(function () {
beforeEach(async function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 }, this.doc)
this.DocstoreManager.getDoc(
await this.DocstoreManager.promises.getDoc(
this.project_id,
this.doc_id,
{ peek: true },
this.callback
{
peek: true,
}
)
})
@@ -323,24 +327,30 @@ describe('DocstoreManager', function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 404 }, '')
this.DocstoreManager.getDoc(this.project_id, this.doc_id, this.callback)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Errors.NotFoundError)
.and(sinon.match.has('message', 'doc not found in docstore'))
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.getDoc(
this.project_id,
this.doc_id
)
.should.equal(true)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Errors.NotFoundError)
expect(error).to.have.property('message', 'doc not found in docstore')
})
})
})
describe('getAllDocs', function () {
describe('with a successful response code', function () {
beforeEach(function () {
let getAllDocsResult
beforeEach(async function () {
this.request.get = sinon
.stub()
.callsArgWith(
@@ -349,7 +359,9 @@ describe('DocstoreManager', function () {
{ statusCode: 204 },
(this.docs = [{ _id: 'mock-doc-id' }])
)
this.DocstoreManager.getAllDocs(this.project_id, this.callback)
getAllDocsResult = await this.DocstoreManager.promises.getAllDocs(
this.project_id
)
})
it('should get all the project docs in the docstore api', function () {
@@ -362,8 +374,8 @@ describe('DocstoreManager', function () {
.should.equal(true)
})
it('should call the callback with the docs', function () {
this.callback.calledWith(null, this.docs).should.equal(true)
it('should return the docs', function () {
expect(getAllDocsResult).to.eql(this.docs)
})
})
@@ -372,35 +384,36 @@ describe('DocstoreManager', function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
this.DocstoreManager.getAllDocs(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'docstore api responded with non-success code: 500'
)
)
)
.should.equal(true)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.getAllDocs(this.project_id)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'docstore api responded with non-success code: 500'
)
})
})
})
describe('getAllDeletedDocs', function () {
describe('with a successful response code', function () {
beforeEach(function (done) {
this.callback.callsFake(done)
let getAllDeletedDocsResponse
beforeEach(async function () {
this.docs = [{ _id: 'mock-doc-id', name: 'foo.tex' }]
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 200 }, this.docs)
this.DocstoreManager.getAllDeletedDocs(this.project_id, this.callback)
getAllDeletedDocsResponse =
await this.DocstoreManager.promises.getAllDeletedDocs(this.project_id)
})
it('should get all the project docs in the docstore api', function () {
@@ -411,48 +424,52 @@ describe('DocstoreManager', function () {
})
})
it('should call the callback with the docs', function () {
this.callback.should.have.been.calledWith(null, this.docs)
it('should resolve with the docs', function () {
expect(getAllDeletedDocsResponse).to.eql(this.docs)
})
})
describe('with an error', function () {
beforeEach(function (done) {
this.callback.callsFake(() => done())
beforeEach(async function () {
this.request.get = sinon
.stub()
.callsArgWith(1, new Error('connect failed'))
this.DocstoreManager.getAllDocs(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
this.callback.should.have.been.calledWith(
sinon.match
.instanceOf(Error)
.and(sinon.match.has('message', 'connect failed'))
)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.getAllDocs(this.project_id)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property('message', 'connect failed')
})
})
describe('with a failed response code', function () {
beforeEach(function (done) {
this.callback.callsFake(() => done())
beforeEach(function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 })
this.DocstoreManager.getAllDocs(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
this.callback.should.have.been.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'docstore api responded with non-success code: 500'
)
)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.getAllDocs(this.project_id)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'docstore api responded with non-success code: 500'
)
})
})
@@ -460,7 +477,8 @@ describe('DocstoreManager', function () {
describe('getAllRanges', function () {
describe('with a successful response code', function () {
beforeEach(function () {
let getAllRangesResult
beforeEach(async function () {
this.request.get = sinon
.stub()
.callsArgWith(
@@ -469,7 +487,9 @@ describe('DocstoreManager', function () {
{ statusCode: 204 },
(this.docs = [{ _id: 'mock-doc-id', ranges: 'mock-ranges' }])
)
this.DocstoreManager.getAllRanges(this.project_id, this.callback)
getAllRangesResult = await this.DocstoreManager.promises.getAllRanges(
this.project_id
)
})
it('should get all the project doc ranges in the docstore api', function () {
@@ -482,8 +502,8 @@ describe('DocstoreManager', function () {
.should.equal(true)
})
it('should call the callback with the docs', function () {
this.callback.calledWith(null, this.docs).should.equal(true)
it('should return the docs', async function () {
expect(getAllRangesResult).to.eql(this.docs)
})
})
@@ -492,22 +512,22 @@ describe('DocstoreManager', function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
this.DocstoreManager.getAllRanges(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'docstore api responded with non-success code: 500'
)
)
)
.should.equal(true)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.getAllRanges(this.project_id)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'docstore api responded with non-success code: 500'
)
})
})
})
@@ -518,11 +538,12 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 })
this.DocstoreManager.archiveProject(this.project_id, this.callback)
})
it('should call the callback', function () {
this.callback.called.should.equal(true)
it('should resolve', async function () {
await expect(
this.DocstoreManager.promises.archiveProject(this.project_id)
).to.eventually.be.fulfilled
})
})
@@ -531,22 +552,22 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 })
this.DocstoreManager.archiveProject(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'docstore api responded with non-success code: 500'
)
)
)
.should.equal(true)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.archiveProject(this.project_id)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'docstore api responded with non-success code: 500'
)
})
})
})
@@ -557,11 +578,12 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 })
this.DocstoreManager.unarchiveProject(this.project_id, this.callback)
})
it('should call the callback', function () {
this.callback.called.should.equal(true)
it('should resolve', async function () {
await expect(
this.DocstoreManager.promises.unarchiveProject(this.project_id)
).to.eventually.be.fulfilled
})
})
@@ -570,22 +592,22 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 })
this.DocstoreManager.unarchiveProject(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'docstore api responded with non-success code: 500'
)
)
)
.should.equal(true)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.unarchiveProject(this.project_id)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'docstore api responded with non-success code: 500'
)
})
})
})
@@ -596,11 +618,12 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 })
this.DocstoreManager.destroyProject(this.project_id, this.callback)
})
it('should call the callback', function () {
this.callback.called.should.equal(true)
it('should resolve', async function () {
await expect(
this.DocstoreManager.promises.destroyProject(this.project_id)
).to.eventually.be.fulfilled
})
})
@@ -609,22 +632,22 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 })
this.DocstoreManager.destroyProject(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(
sinon.match.has(
'message',
'docstore api responded with non-success code: 500'
)
)
)
.should.equal(true)
it('should reject with an error', async function () {
let error
try {
await this.DocstoreManager.promises.destroyProject(this.project_id)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'docstore api responded with non-success code: 500'
)
})
})
})

View File

@@ -95,7 +95,7 @@ describe('FileStoreHandler', function () {
this.request.returns(this.writeStream)
})
it('should get the project details', function (done) {
it('should get the project details', async function () {
this.fs.createReadStream.returns({
pipe() {},
on(type, cb) {
@@ -104,20 +104,17 @@ describe('FileStoreHandler', function () {
}
},
})
this.handler.uploadFileFromDisk(
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {
this.ProjectDetailsHandler.getDetails
.calledWith(this.projectId)
.should.equal(true)
done()
}
this.fsPath
)
this.ProjectDetailsHandler.getDetails
.calledWith(this.projectId)
.should.equal(true)
})
it('should compute the file hash', function (done) {
it('should compute the file hash', async function () {
this.fs.createReadStream.returns({
pipe() {},
on(type, cb) {
@@ -126,21 +123,18 @@ describe('FileStoreHandler', function () {
}
},
})
this.handler.uploadFileFromDisk(
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {
this.FileHashManager.computeHash
.calledWith(this.fsPath)
.should.equal(true)
done()
}
this.fsPath
)
this.FileHashManager.computeHash
.calledWith(this.fsPath)
.should.equal(true)
})
describe('when project-history-blobs feature is enabled', function () {
it('should upload the file to the history store as a blob', function (done) {
it('should upload the file to the history store as a blob', async function () {
this.fs.createReadStream.returns({
pipe() {},
on(type, cb) {
@@ -150,26 +144,23 @@ describe('FileStoreHandler', function () {
},
})
this.Features.hasFeature.withArgs('project-history-blobs').returns(true)
this.handler.uploadFileFromDisk(
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {
this.HistoryManager.uploadBlobFromDisk
.calledWith(
this.historyId,
this.hashValue,
this.fileSize,
this.fsPath
)
.should.equal(true)
done()
}
this.fsPath
)
this.HistoryManager.uploadBlobFromDisk
.calledWith(
this.historyId,
this.hashValue,
this.fileSize,
this.fsPath
)
.should.equal(true)
})
})
describe('when project-history-blobs feature is disabled', function () {
it('should not upload the file to the history store as a blob', function (done) {
it('should not upload the file to the history store as a blob', async function () {
this.fs.createReadStream.returns({
pipe() {},
on(type, cb) {
@@ -178,15 +169,12 @@ describe('FileStoreHandler', function () {
}
},
})
this.handler.uploadFileFromDisk(
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {
this.HistoryManager.uploadBlobFromDisk.called.should.equal(false)
done()
}
this.fsPath
)
this.HistoryManager.uploadBlobFromDisk.called.should.equal(false)
})
})
@@ -214,71 +202,63 @@ describe('FileStoreHandler', function () {
)
})
it('should pipe the read stream to request', function (done) {
it('should pipe the read stream to request', function () {
this.request.returns(this.writeStream)
this.fs.createReadStream.returns({
on(type, cb) {
if (type === 'open') {
cb()
}
},
pipe: o => {
this.writeStream.should.equal(o)
done()
},
return new Promise((resolve, reject) => {
this.fs.createReadStream.returns({
on(type, cb) {
if (type === 'open') {
cb()
}
},
pipe: o => {
this.writeStream.should.equal(o)
resolve()
},
})
this.handler.promises
.uploadFileFromDisk(this.projectId, this.fileArgs, this.fsPath)
.catch(reject)
})
this.handler.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {}
)
})
it('should pass the correct options to request', function (done) {
it('should pass the correct options to request', async function () {
const fileUrl = this.getFileUrl(this.projectId, this.fileId)
this.fs.createReadStream.returns({
pipe() {},
on(type, cb) {
pipe: sinon.stub(),
on: sinon.stub((type, cb) => {
if (type === 'open') {
cb()
}
},
}),
})
this.handler.uploadFileFromDisk(
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {
this.request.args[0][0].method.should.equal('post')
this.request.args[0][0].uri.should.equal(fileUrl)
done()
}
this.fsPath
)
this.request.args[0][0].method.should.equal('post')
this.request.args[0][0].uri.should.equal(fileUrl)
})
it('should callback with the url and fileRef', function (done) {
it('should resolve with the url and fileRef', async function () {
const fileUrl = this.getFileUrl(this.projectId, this.fileId)
this.fs.createReadStream.returns({
pipe() {},
on(type, cb) {
pipe: sinon.stub(),
on: sinon.stub((type, cb) => {
if (type === 'open') {
cb()
}
},
}),
})
this.handler.uploadFileFromDisk(
const { url, fileRef } = await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
(err, url, fileRef) => {
expect(err).to.not.exist
expect(url).to.equal(fileUrl)
expect(fileRef._id).to.equal(this.fileId)
expect(fileRef.hash).to.equal(this.hashValue)
done()
}
this.fsPath
)
expect(url).to.equal(fileUrl)
expect(fileRef._id).to.equal(this.fileId)
expect(fileRef.hash).to.equal(this.hashValue)
})
describe('when upload to filestore fails', function () {
beforeEach(function () {
@@ -289,28 +269,32 @@ describe('FileStoreHandler', function () {
}
})
it('should callback with an error', function (done) {
it('should reject with an error', async function () {
this.fs.createReadStream.callCount = 0
this.fs.createReadStream.returns({
pipe() {},
on(type, cb) {
pipe: sinon.stub(),
on: sinon.stub((type, cb) => {
if (type === 'open') {
cb()
}
},
}),
})
this.handler.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
err => {
expect(err).to.exist
expect(err).to.be.instanceof(Error)
expect(this.fs.createReadStream.callCount).to.equal(
this.handler.RETRY_ATTEMPTS
)
done()
}
let error
try {
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(this.fs.createReadStream.callCount).to.equal(
this.handler.RETRY_ATTEMPTS
)
})
})
@@ -319,49 +303,40 @@ describe('FileStoreHandler', function () {
beforeEach(function () {
this.Features.hasFeature.withArgs('filestore').returns(false)
})
it('should not open file handle', function (done) {
this.handler.uploadFileFromDisk(
it('should not open file handle', async function () {
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {
expect(this.fs.createReadStream).to.not.have.been.called
done()
}
this.fsPath
)
expect(this.fs.createReadStream).to.not.have.been.called
})
it('should not talk to filestore', function (done) {
this.handler.uploadFileFromDisk(
it('should not talk to filestore', async function () {
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {
expect(this.request).to.not.have.been.called
done()
}
this.fsPath
)
expect(this.request).to.not.have.been.called
})
it('should callback with the url and fileRef', function (done) {
it('should resolve with the url and fileRef', async function () {
const fileUrl = this.getFileUrl(this.projectId, this.fileId)
this.handler.uploadFileFromDisk(
const { url, fileRef } = await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
(err, url, fileRef) => {
expect(err).to.not.exist
expect(url).to.equal(fileUrl)
expect(fileRef._id).to.equal(this.fileId)
expect(fileRef.hash).to.equal(this.hashValue)
done()
}
this.fsPath
)
expect(url).to.equal(fileUrl)
expect(fileRef._id).to.equal(this.fileId)
expect(fileRef.hash).to.equal(this.hashValue)
})
})
describe('symlink', function () {
it('should not read file if it is symlink', function (done) {
it('should not read file if it is symlink', async function () {
this.fs.lstat = sinon.stub().callsArgWith(1, null, {
isFile() {
return false
@@ -371,28 +346,40 @@ describe('FileStoreHandler', function () {
},
})
this.handler.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {
this.fs.createReadStream.called.should.equal(false)
done()
}
)
let error
try {
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath
)
} catch (err) {
error = err
}
expect(error).to.exist
this.fs.createReadStream.called.should.equal(false)
})
it('should not read file stat returns nothing', function (done) {
it('should not read file stat returns nothing', async function () {
this.fs.lstat = sinon.stub().callsArgWith(1, null, null)
this.handler.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath,
() => {
this.fs.createReadStream.called.should.equal(false)
done()
}
)
let error
try {
await this.handler.promises.uploadFileFromDisk(
this.projectId,
this.fileArgs,
this.fsPath
)
} catch (err) {
error = err
}
expect(error).to.exist
this.fs.createReadStream.called.should.equal(false)
})
})
})
@@ -410,13 +397,18 @@ describe('FileStoreHandler', function () {
})
})
it('should return the error if there is one', function (done) {
const error = 'my error'
this.request.callsArgWith(1, error)
this.handler.deleteFile(this.projectId, this.fileId, err => {
assert.equal(err, error)
done()
})
it('should reject with the error if there is one', async function () {
const expectedError = 'my error'
this.request.callsArgWith(1, expectedError)
let error
try {
await this.handler.promises.deleteFile(this.projectId, this.fileId)
} catch (err) {
error = err
}
expect(error).to.equal(expectedError)
})
})
@@ -425,40 +417,43 @@ describe('FileStoreHandler', function () {
beforeEach(function () {
this.Features.hasFeature.withArgs('filestore').returns(true)
})
it('should send a delete request to filestore api', function (done) {
it('should send a delete request to filestore api', async function () {
const projectUrl = this.getProjectUrl(this.projectId)
this.request.callsArgWith(1, null)
this.handler.deleteProject(this.projectId, err => {
assert.equal(err, undefined)
this.request.args[0][0].method.should.equal('delete')
this.request.args[0][0].uri.should.equal(projectUrl)
done()
})
await this.handler.promises.deleteProject(this.projectId)
this.request.args[0][0].method.should.equal('delete')
this.request.args[0][0].uri.should.equal(projectUrl)
})
it('should wrap the error if there is one', function (done) {
const error = new Error('my error')
this.request.callsArgWith(1, error)
this.handler.deleteProject(this.projectId, err => {
expect(OError.getFullStack(err)).to.match(
/something went wrong deleting a project in filestore/
)
expect(OError.getFullStack(err)).to.match(/my error/)
done()
})
it('should wrap the error if there is one', async function () {
const expectedError = new Error('my error')
this.request.callsArgWith(1, expectedError)
const promise = this.handler.promises.deleteProject(this.projectId)
let error
try {
await promise
} catch (err) {
error = err
}
expect(error).to.exist
expect(OError.getFullStack(error)).to.match(
/something went wrong deleting a project in filestore/
)
expect(OError.getFullStack(error)).to.match(/my error/)
})
})
describe('when filestore is disabled', function () {
beforeEach(function () {
this.Features.hasFeature.withArgs('filestore').returns(false)
})
it('should not send a delete request to filestore api', function (done) {
this.handler.deleteProject(this.projectId, err => {
assert.equal(err, undefined)
this.request.called.should.equal(false)
done()
})
it('should not send a delete request to filestore api', async function () {
await this.handler.promises.deleteProject(this.projectId)
this.request.called.should.equal(false)
})
})
})
@@ -469,18 +464,37 @@ describe('FileStoreHandler', function () {
this.Features.hasFeature.withArgs('filestore').returns(false)
})
it('should callback with a NotFoundError', function (done) {
this.handler.getFileStream(this.projectId, this.fileId, {}, err => {
expect(err).to.be.instanceof(Errors.NotFoundError)
done()
})
it('should callback with a NotFoundError', async function () {
let error
try {
await this.handler.promises.getFileStream(
this.projectId,
this.fileId,
{}
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Errors.NotFoundError)
})
it('should not call request', function (done) {
this.handler.getFileStream(this.projectId, this.fileId, {}, () => {
this.request.called.should.equal(false)
done()
})
it('should not call request', async function () {
let error
try {
await this.handler.promises.getFileStream(
this.projectId,
this.fileId,
{}
)
} catch (err) {
error = err
}
expect(error).to.exist
this.request.called.should.equal(false)
})
})
describe('when filestore is enabled', function () {
@@ -490,53 +504,36 @@ describe('FileStoreHandler', function () {
this.Features.hasFeature.withArgs('filestore').returns(true)
})
it('should get the stream with the correct params', function (done) {
it('should get the stream with the correct params', async function () {
const fileUrl = this.getFileUrl(this.projectId, this.fileId)
this.handler.getFileStream(
await this.handler.promises.getFileStream(
this.projectId,
this.fileId,
this.query,
(err, stream) => {
if (err) {
return done(err)
}
this.request.args[0][0].method.should.equal('get')
this.request.args[0][0].uri.should.equal(
fileUrl + '?from=getFileStream'
)
done()
}
this.query
)
this.request.args[0][0].method.should.equal('get')
this.request.args[0][0].uri.should.equal(
fileUrl + '?from=getFileStream'
)
})
it('should get stream from request', function (done) {
this.handler.getFileStream(
it('should get stream from request', async function () {
const stream = await this.handler.promises.getFileStream(
this.projectId,
this.fileId,
this.query,
(err, stream) => {
if (err) {
return done(err)
}
stream.should.equal(this.readStream)
done()
}
this.query
)
stream.should.equal(this.readStream)
})
it('should add an error handler', function (done) {
this.handler.getFileStream(
it('should add an error handler', async function () {
const stream = await this.handler.promises.getFileStream(
this.projectId,
this.fileId,
this.query,
(err, stream) => {
if (err) {
return done(err)
}
stream.on.calledWith('error').should.equal(true)
done()
}
this.query
)
stream.on.calledWith('error').should.equal(true)
})
describe('when range is specified in query', function () {
@@ -544,22 +541,17 @@ describe('FileStoreHandler', function () {
this.query = { range: '0-10' }
})
it('should add a range header', function (done) {
this.handler.getFileStream(
it('should add a range header', async function () {
await this.handler.promises.getFileStream(
this.projectId,
this.fileId,
this.query,
(err, stream) => {
if (err) {
return done(err)
}
this.request.callCount.should.equal(1)
const { headers } = this.request.firstCall.args[0]
expect(headers).to.have.keys('range')
expect(headers.range).to.equal('bytes=0-10')
done()
}
this.query
)
this.request.callCount.should.equal(1)
const { headers } = this.request.firstCall.args[0]
expect(headers).to.have.keys('range')
expect(headers.range).to.equal('bytes=0-10')
})
describe('when range is invalid', function () {
@@ -568,21 +560,15 @@ describe('FileStoreHandler', function () {
this.query = { range: `${r}` }
})
it(`should not add a range header for '${r}'`, function (done) {
this.handler.getFileStream(
it(`should not add a range header for '${r}'`, async function () {
await this.handler.promises.getFileStream(
this.projectId,
this.fileId,
this.query,
(err, stream) => {
if (err) {
return done(err)
}
this.request.callCount.should.equal(1)
const { headers } = this.request.firstCall.args[0]
expect(headers).to.not.have.keys('range')
done()
}
this.query
)
this.request.callCount.should.equal(1)
const { headers } = this.request.firstCall.args[0]
expect(headers).to.not.have.keys('range')
})
})
})
@@ -591,7 +577,7 @@ describe('FileStoreHandler', function () {
})
describe('getFileSize', function () {
it('returns the file size reported by filestore', function (done) {
it('returns the file size reported by filestore', async function () {
const expectedFileSize = 32432
const fileUrl =
this.getFileUrl(this.projectId, this.fileId) + '?from=getFileSize'
@@ -605,40 +591,54 @@ describe('FileStoreHandler', function () {
},
})
this.handler.getFileSize(this.projectId, this.fileId, (err, fileSize) => {
if (err) {
return done(err)
}
expect(fileSize).to.equal(expectedFileSize)
done()
})
const fileSize = await this.handler.promises.getFileSize(
this.projectId,
this.fileId
)
expect(fileSize).to.equal(expectedFileSize)
})
it('throws a NotFoundError on a 404 from filestore', function (done) {
it('throws a NotFoundError on a 404 from filestore', async function () {
this.request.head.yields(null, { statusCode: 404 })
this.handler.getFileSize(this.projectId, this.fileId, err => {
expect(err).to.be.instanceof(Errors.NotFoundError)
done()
})
let error
try {
await this.handler.promises.getFileSize(this.projectId, this.fileId)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Errors.NotFoundError)
})
it('throws an error on a non-200 from filestore', function (done) {
it('throws an error on a non-200 from filestore', async function () {
this.request.head.yields(null, { statusCode: 500 })
this.handler.getFileSize(this.projectId, this.fileId, err => {
expect(err).to.be.instanceof(Error)
done()
})
let error
try {
await this.handler.promises.getFileSize(this.projectId, this.fileId)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
})
it('rethrows errors from filestore', function (done) {
this.request.head.yields(new Error())
it('rethrows errors from filestore', async function () {
const expectedError = new Error('from filestore')
this.request.head.yields(expectedError)
this.handler.getFileSize(this.projectId, this.fileId, err => {
expect(err).to.be.instanceof(Error)
done()
})
let error
try {
await this.handler.promises.getFileSize(this.projectId, this.fileId)
} catch (err) {
error = err
}
expect(error).to.equal(expectedError)
})
})
@@ -648,74 +648,75 @@ describe('FileStoreHandler', function () {
this.newFileId = 'new file id'
})
it('should post json', function (done) {
it('should post json', async function () {
const newFileUrl = this.getFileUrl(this.newProjectId, this.newFileId)
this.request.callsArgWith(1, null, { statusCode: 200 })
this.handler.copyFile(
await this.handler.promises.copyFile(
this.projectId,
this.fileId,
this.newProjectId,
this.newFileId,
() => {
this.request.args[0][0].method.should.equal('put')
this.request.args[0][0].uri.should.equal(newFileUrl)
this.request.args[0][0].json.source.project_id.should.equal(
this.projectId
)
this.request.args[0][0].json.source.file_id.should.equal(this.fileId)
done()
}
this.newFileId
)
this.request.args[0][0].method.should.equal('put')
this.request.args[0][0].uri.should.equal(newFileUrl)
this.request.args[0][0].json.source.project_id.should.equal(
this.projectId
)
this.request.args[0][0].json.source.file_id.should.equal(this.fileId)
})
it('returns the url', function (done) {
it('returns the url', async function () {
const expectedUrl = this.getFileUrl(this.newProjectId, this.newFileId)
this.request.callsArgWith(1, null, { statusCode: 200 })
this.handler.copyFile(
const url = await this.handler.promises.copyFile(
this.projectId,
this.fileId,
this.newProjectId,
this.newFileId,
(err, url) => {
if (err) {
return done(err)
}
url.should.equal(expectedUrl)
done()
}
this.newFileId
)
url.should.equal(expectedUrl)
})
it('should return the err', function (done) {
const error = new Error('error')
this.request.callsArgWith(1, error)
this.handler.copyFile(
this.projectId,
this.fileId,
this.newProjectId,
this.newFileId,
err => {
err.should.equal(error)
done()
}
)
it('should return the err', async function () {
const expectedError = new Error('error')
this.request.callsArgWith(1, expectedError)
let error
try {
await this.handler.promises.copyFile(
this.projectId,
this.fileId,
this.newProjectId,
this.newFileId
)
} catch (err) {
error = err
}
expect(error).to.equal(expectedError)
})
it('should return an error for a non-success statusCode', function (done) {
it('should return an error for a non-success statusCode', async function () {
this.request.callsArgWith(1, null, { statusCode: 500 })
this.handler.copyFile(
this.projectId,
this.fileId,
this.newProjectId,
this.newFileId,
err => {
err.should.be.an('error')
err.message.should.equal(
'non-ok response from filestore for copyFile: 500'
)
done()
}
let error
try {
await this.handler.promises.copyFile(
this.projectId,
this.fileId,
this.newProjectId,
this.newFileId
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
'non-ok response from filestore for copyFile: 500'
)
})
})

View File

@@ -45,48 +45,43 @@ describe('InstitutionsAPI', function () {
})
describe('getInstitutionAffiliations', function () {
it('get affiliations', function (done) {
it('get affiliations', async function () {
this.institutionId = 123
const responseBody = ['123abc', '456def']
this.request.yields(null, { statusCode: 200 }, responseBody)
this.InstitutionsAPI.getInstitutionAffiliations(
this.institutionId,
(err, body) => {
expect(err).not.to.exist
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl = `v1.url/api/v2/institutions/${this.institutionId}/affiliations`
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('GET')
requestOptions.maxAttempts.should.exist
requestOptions.maxAttempts.should.not.equal(0)
requestOptions.retryDelay.should.exist
expect(requestOptions.body).not.to.exist
body.should.equal(responseBody)
done()
}
)
const body =
await this.InstitutionsAPI.promises.getInstitutionAffiliations(
this.institutionId
)
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl = `v1.url/api/v2/institutions/${this.institutionId}/affiliations`
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('GET')
requestOptions.maxAttempts.should.exist
requestOptions.maxAttempts.should.not.equal(0)
requestOptions.retryDelay.should.exist
expect(requestOptions.body).not.to.exist
body.should.equal(responseBody)
})
it('handle empty response', function (done) {
it('handle empty response', async function () {
this.settings.apis.v1.url = ''
this.InstitutionsAPI.getInstitutionAffiliations(
this.institutionId,
(err, body) => {
expect(err).not.to.exist
expect(body).to.be.a('Array')
body.length.should.equal(0)
done()
}
)
const body =
await this.InstitutionsAPI.promises.getInstitutionAffiliations(
this.institutionId
)
expect(body).to.be.a('Array')
body.length.should.equal(0)
})
})
describe('getLicencesForAnalytics', function () {
const lag = 'daily'
const queryDate = '2017-01-07:00:00.000Z'
it('should send the request to v1', function (done) {
it('should send the request to v1', async function () {
const v1Result = {
lag: 'daily',
date: queryDate,
@@ -96,100 +91,91 @@ describe('InstitutionsAPI', function () {
},
}
this.request.callsArgWith(1, null, { statusCode: 201 }, v1Result)
this.InstitutionsAPI.getLicencesForAnalytics(
await this.InstitutionsAPI.promises.getLicencesForAnalytics(
lag,
queryDate,
(error, result) => {
expect(error).not.to.exist
const requestOptions = this.request.lastCall.args[0]
expect(requestOptions.body.query_date).to.equal(queryDate)
expect(requestOptions.body.lag).to.equal(lag)
requestOptions.method.should.equal('GET')
done()
}
queryDate
)
const requestOptions = this.request.lastCall.args[0]
expect(requestOptions.body.query_date).to.equal(queryDate)
expect(requestOptions.body.lag).to.equal(lag)
requestOptions.method.should.equal('GET')
})
it('should handle errors', function (done) {
it('should handle errors', async function () {
this.request.callsArgWith(1, null, { statusCode: 500 })
this.InstitutionsAPI.getLicencesForAnalytics(
lag,
queryDate,
(error, result) => {
expect(error).to.be.instanceof(Errors.V1ConnectionError)
done()
}
)
let error
try {
await this.InstitutionsAPI.promises.getLicencesForAnalytics(
lag,
queryDate
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Errors.V1ConnectionError)
})
})
describe('getUserAffiliations', function () {
it('get affiliations', function (done) {
it('get affiliations', async function () {
const responseBody = [{ foo: 'bar' }]
this.request.callsArgWith(1, null, { statusCode: 201 }, responseBody)
this.InstitutionsAPI.getUserAffiliations(
this.stubbedUser._id,
(err, body) => {
expect(err).not.to.exist
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('GET')
requestOptions.maxAttempts.should.equal(3)
expect(requestOptions.body).not.to.exist
body.should.equal(responseBody)
done()
}
const body = await this.InstitutionsAPI.promises.getUserAffiliations(
this.stubbedUser._id
)
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('GET')
requestOptions.maxAttempts.should.equal(3)
expect(requestOptions.body).not.to.exist
body.should.equal(responseBody)
})
it('handle error', function (done) {
it('handle error', async function () {
const body = { errors: 'affiliation error message' }
this.request.callsArgWith(1, null, { statusCode: 503 }, body)
this.InstitutionsAPI.getUserAffiliations(this.stubbedUser._id, err => {
expect(err).to.be.instanceof(Errors.V1ConnectionError)
done()
})
let error
try {
await this.InstitutionsAPI.promises.getUserAffiliations(
this.stubbedUser._id
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Errors.V1ConnectionError)
})
it('handle empty response', function (done) {
it('handle empty response', async function () {
this.settings.apis.v1.url = ''
this.InstitutionsAPI.getUserAffiliations(
this.stubbedUser._id,
(err, body) => {
expect(err).not.to.exist
expect(body).to.be.a('Array')
body.length.should.equal(0)
done()
}
const body = await this.InstitutionsAPI.promises.getUserAffiliations(
this.stubbedUser._id
)
expect(body).to.be.a('Array')
body.length.should.equal(0)
})
})
describe('getUsersNeedingReconfirmationsLapsedProcessed', function () {
it('get the list of users', function (done) {
it('get the list of users', async function () {
this.fetchJson.resolves({ statusCode: 200 })
this.InstitutionsAPI.getUsersNeedingReconfirmationsLapsedProcessed(
error => {
expect(error).not.to.exist
this.fetchJson.calledOnce.should.equal(true)
const requestOptions = this.fetchJson.lastCall.args[1]
const expectedUrl = `v1.url/api/v2/institutions/need_reconfirmation_lapsed_processed`
this.fetchJson.lastCall.args[0].should.equal(expectedUrl)
requestOptions.method.should.equal('GET')
done()
}
)
await this.InstitutionsAPI.promises.getUsersNeedingReconfirmationsLapsedProcessed()
this.fetchJson.calledOnce.should.equal(true)
const requestOptions = this.fetchJson.lastCall.args[1]
const expectedUrl = `v1.url/api/v2/institutions/need_reconfirmation_lapsed_processed`
this.fetchJson.lastCall.args[0].should.equal(expectedUrl)
requestOptions.method.should.equal('GET')
})
it('handle error', function (done) {
it('handle error', async function () {
this.fetchJson.throws({ info: { statusCode: 500 } })
this.InstitutionsAPI.getUsersNeedingReconfirmationsLapsedProcessed(
error => {
expect(error).to.exist
done()
}
)
await expect(
this.InstitutionsAPI.promises.getUsersNeedingReconfirmationsLapsedProcessed()
).to.be.rejected
})
})
@@ -198,7 +184,7 @@ describe('InstitutionsAPI', function () {
this.fetchNothing.resolves({ status: 201 })
})
it('add affiliation', function (done) {
it('add affiliation', async function () {
const affiliationOptions = {
university: { id: 1 },
department: 'Math',
@@ -206,95 +192,104 @@ describe('InstitutionsAPI', function () {
confirmedAt: new Date(),
entitlement: true,
}
this.InstitutionsAPI.addAffiliation(
await this.InstitutionsAPI.promises.addAffiliation(
this.stubbedUser._id,
this.newEmail,
affiliationOptions,
err => {
expect(err).not.to.exist
this.fetchNothing.calledOnce.should.equal(true)
const requestOptions = this.fetchNothing.lastCall.args[1]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
expect(this.fetchNothing.lastCall.args[0]).to.equal(expectedUrl)
requestOptions.method.should.equal('POST')
const { json } = requestOptions
Object.keys(json).length.should.equal(7)
expect(json).to.deep.equal(
Object.assign(
{ email: this.newEmail, rejectIfBlocklisted: undefined },
affiliationOptions
)
)
this.markAsReadIpMatcher.calledOnce.should.equal(true)
done()
}
affiliationOptions
)
this.fetchNothing.calledOnce.should.equal(true)
const requestOptions = this.fetchNothing.lastCall.args[1]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
expect(this.fetchNothing.lastCall.args[0]).to.equal(expectedUrl)
requestOptions.method.should.equal('POST')
const { json } = requestOptions
Object.keys(json).length.should.equal(7)
expect(json).to.deep.equal(
Object.assign(
{ email: this.newEmail, rejectIfBlocklisted: undefined },
affiliationOptions
)
)
this.markAsReadIpMatcher.calledOnce.should.equal(true)
})
it('handles 422 error', function (done) {
it('handles 422 error', async function () {
const messageFromApi = 'affiliation error message'
const body = JSON.stringify({ errors: messageFromApi })
this.fetchNothing.throws({ response: { status: 422 }, body })
this.InstitutionsAPI.addAffiliation(
this.stubbedUser._id,
this.newEmail,
{},
err => {
expect(err).to.exist
expect(err).to.be.instanceOf(Errors.InvalidInstitutionalEmailError)
err.message.should.have.string(422)
err.message.should.have.string(messageFromApi)
done()
}
)
let error
try {
await this.InstitutionsAPI.promises.addAffiliation(
this.stubbedUser._id,
this.newEmail,
{}
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Errors.InvalidInstitutionalEmailError)
expect(error).to.have.property('message', `422: ${messageFromApi}`)
})
it('handles 500 error', function (done) {
it('handles 500 error', async function () {
const body = { errors: 'affiliation error message' }
this.fetchNothing.throws({ response: { status: 500 }, body })
this.InstitutionsAPI.addAffiliation(
this.stubbedUser._id,
this.newEmail,
{},
err => {
expect(err).to.be.instanceOf(Errors.V1ConnectionError)
expect(err.message).to.equal('error getting affiliations from v1')
expect(err.info).to.deep.equal({ status: 500, body })
done()
}
)
let error
try {
await this.InstitutionsAPI.promises.addAffiliation(
this.stubbedUser._id,
this.newEmail,
{}
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Errors.V1ConnectionError)
expect(error.message).to.equal('error getting affiliations from v1')
expect(error.info).to.eql({
status: 500,
body: { errors: 'affiliation error message' },
})
})
it('uses default error message when no error body in response', function (done) {
it('uses default error message when no error body in response', async function () {
this.fetchNothing.throws({ response: { status: 429 } })
this.InstitutionsAPI.addAffiliation(
this.stubbedUser._id,
this.newEmail,
{},
err => {
expect(err).to.exist
expect(err.message).to.equal("Couldn't create affiliation: 429")
done()
}
let error
try {
await this.InstitutionsAPI.promises.addAffiliation(
this.stubbedUser._id,
this.newEmail,
{}
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Error)
expect(error).to.have.property(
'message',
"Couldn't create affiliation: 429"
)
})
it('does not try to mark IP matcher notifications as read if no university passed', function (done) {
it('does not try to mark IP matcher notifications as read if no university passed', async function () {
const affiliationOptions = {
confirmedAt: new Date(),
}
this.InstitutionsAPI.addAffiliation(
await this.InstitutionsAPI.promises.addAffiliation(
this.stubbedUser._id,
this.newEmail,
affiliationOptions,
err => {
expect(err).not.to.exist
expect(this.markAsReadIpMatcher.callCount).to.equal(0)
done()
}
affiliationOptions
)
expect(this.markAsReadIpMatcher.callCount).to.equal(0)
})
})
@@ -303,58 +298,64 @@ describe('InstitutionsAPI', function () {
this.fetchNothing.throws({ response: { status: 404 } })
})
it('remove affiliation', function (done) {
this.InstitutionsAPI.removeAffiliation(
it('remove affiliation', async function () {
await this.InstitutionsAPI.promises.removeAffiliation(
this.stubbedUser._id,
this.newEmail,
err => {
expect(err).not.to.exist
this.fetchNothing.calledOnce.should.equal(true)
const requestOptions = this.fetchNothing.lastCall.args[1]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations/remove`
this.fetchNothing.lastCall.args[0].should.equal(expectedUrl)
requestOptions.method.should.equal('POST')
expect(requestOptions.json).to.deep.equal({ email: this.newEmail })
done()
}
this.newEmail
)
this.fetchNothing.calledOnce.should.equal(true)
const requestOptions = this.fetchNothing.lastCall.args[1]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations/remove`
this.fetchNothing.lastCall.args[0].should.equal(expectedUrl)
requestOptions.method.should.equal('POST')
expect(requestOptions.json).to.deep.equal({ email: this.newEmail })
})
it('handle error', function (done) {
it('handle error', async function () {
this.fetchNothing.throws({ response: { status: 500 } })
this.InstitutionsAPI.removeAffiliation(
this.stubbedUser._id,
this.newEmail,
err => {
expect(err).to.exist
err.message.should.exist
done()
}
)
let error
try {
await this.InstitutionsAPI.promises.removeAffiliation(
this.stubbedUser._id,
this.newEmail
)
} catch (err) {
error = err
}
expect(error).to.exist
expect(error).to.have.property('message')
})
})
describe('deleteAffiliations', function () {
it('delete affiliations', function (done) {
it('delete affiliations', async function () {
this.request.callsArgWith(1, null, { statusCode: 200 })
this.InstitutionsAPI.deleteAffiliations(this.stubbedUser._id, err => {
expect(err).not.to.exist
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('DELETE')
done()
})
await this.InstitutionsAPI.promises.deleteAffiliations(
this.stubbedUser._id
)
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('DELETE')
})
it('handle error', function (done) {
it('handle error', async function () {
const body = { errors: 'affiliation error message' }
this.request.callsArgWith(1, null, { statusCode: 518 }, body)
this.InstitutionsAPI.deleteAffiliations(this.stubbedUser._id, err => {
expect(err).to.be.instanceof(Errors.V1ConnectionError)
done()
})
let error
try {
await this.InstitutionsAPI.promises.deleteAffiliations(
this.stubbedUser._id
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Errors.V1ConnectionError)
})
})
@@ -363,61 +364,57 @@ describe('InstitutionsAPI', function () {
this.request.callsArgWith(1, null, { statusCode: 204 })
})
it('endorse affiliation', function (done) {
this.InstitutionsAPI.endorseAffiliation(
it('endorse affiliation', async function () {
await this.InstitutionsAPI.promises.endorseAffiliation(
this.stubbedUser._id,
this.newEmail,
'Student',
'Physics',
err => {
expect(err).not.to.exist
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations/endorse`
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('POST')
const { body } = requestOptions
Object.keys(body).length.should.equal(3)
body.email.should.equal(this.newEmail)
body.role.should.equal('Student')
body.department.should.equal('Physics')
done()
}
'Physics'
)
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations/endorse`
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('POST')
const { body } = requestOptions
Object.keys(body).length.should.equal(3)
body.email.should.equal(this.newEmail)
body.role.should.equal('Student')
body.department.should.equal('Physics')
})
})
describe('sendUsersWithReconfirmationsLapsedProcessed', function () {
const users = ['abc123', 'def456']
it('sends the list of users', function (done) {
it('sends the list of users', async function () {
this.request.callsArgWith(1, null, { statusCode: 200 })
this.InstitutionsAPI.sendUsersWithReconfirmationsLapsedProcessed(
users,
error => {
expect(error).not.to.exist
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl =
'v1.url/api/v2/institutions/reconfirmation_lapsed_processed'
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('POST')
expect(requestOptions.body).to.deep.equal({ users })
done()
}
await this.InstitutionsAPI.promises.sendUsersWithReconfirmationsLapsedProcessed(
users
)
this.request.calledOnce.should.equal(true)
const requestOptions = this.request.lastCall.args[0]
const expectedUrl =
'v1.url/api/v2/institutions/reconfirmation_lapsed_processed'
requestOptions.url.should.equal(expectedUrl)
requestOptions.method.should.equal('POST')
expect(requestOptions.body).to.deep.equal({ users })
})
it('handle error', function (done) {
it('handle error', async function () {
this.request.callsArgWith(1, null, { statusCode: 500 })
this.InstitutionsAPI.sendUsersWithReconfirmationsLapsedProcessed(
users,
error => {
expect(error).to.exist
done()
}
)
let error
try {
await this.InstitutionsAPI.promises.sendUsersWithReconfirmationsLapsedProcessed(
users
)
} catch (err) {
error = err
}
expect(error).to.exist
})
})
})

View File

@@ -1,17 +1,4 @@
/* 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 assert = require('assert')
const { expect } = require('chai')
const sinon = require('sinon')
const modulePath = require('path').join(
@@ -41,45 +28,39 @@ describe('InstitutionsFeatures', function () {
})
describe('hasLicence', function () {
it('should handle error', function (done) {
it('should handle error', async function () {
this.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
return this.InstitutionsFeatures.hasLicence(
this.userId,
(error, hasLicence) => {
expect(error).to.exist
return done()
}
)
let error
try {
await this.InstitutionsFeatures.promises.hasLicence(this.userId)
} catch (err) {
error = err
}
expect(error).to.exist
})
it('should return false if user has no paid affiliations', function (done) {
it('should return false if user has no paid affiliations', async function () {
this.UserGetter.promises.getUserFullEmails.resolves(
this.emailDataWithoutLicense
)
return this.InstitutionsFeatures.hasLicence(
this.userId,
(error, hasLicence) => {
expect(error).to.not.exist
expect(hasLicence).to.be.false
return done()
}
const hasLicence = await this.InstitutionsFeatures.promises.hasLicence(
this.userId
)
expect(hasLicence).to.be.false
})
it('should return true if user has confirmed paid affiliation', function (done) {
it('should return true if user has confirmed paid affiliation', async function () {
const emailData = [
{ emailHasInstitutionLicence: true },
{ emailHasInstitutionLicence: false },
]
this.UserGetter.promises.getUserFullEmails.resolves(emailData)
return this.InstitutionsFeatures.hasLicence(
this.userId,
(error, hasLicence) => {
expect(error).to.not.exist
expect(hasLicence).to.be.true
return done()
}
const hasLicence = await this.InstitutionsFeatures.promises.hasLicence(
this.userId
)
expect(hasLicence).to.be.true
})
})
@@ -91,84 +72,62 @@ describe('InstitutionsFeatures', function () {
.returns(this.testFeatures)
})
it('should handle error', function (done) {
it('should handle error', async function () {
this.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
return this.InstitutionsFeatures.getInstitutionsFeatures(
this.userId,
(error, features) => {
expect(error).to.exist
return done()
}
)
await expect(
this.InstitutionsFeatures.promises.getInstitutionsFeatures(this.userId)
).to.be.rejected
})
it('should return no feaures if user has no plan code', function (done) {
it('should return no feaures if user has no plan code', async function () {
this.UserGetter.promises.getUserFullEmails.resolves(
this.emailDataWithoutLicense
)
return this.InstitutionsFeatures.getInstitutionsFeatures(
this.userId,
(error, features) => {
expect(error).to.not.exist
expect(features).to.deep.equal({})
return done()
}
)
const features =
await this.InstitutionsFeatures.promises.getInstitutionsFeatures(
this.userId
)
expect(features).to.deep.equal({})
})
it('should return feaures if user has affiliations plan code', function (done) {
it('should return feaures if user has affiliations plan code', async function () {
this.UserGetter.promises.getUserFullEmails.resolves(
this.emailDataWithLicense
)
return this.InstitutionsFeatures.getInstitutionsFeatures(
this.userId,
(error, features) => {
expect(error).to.not.exist
expect(features).to.deep.equal(this.testFeatures.features)
return done()
}
)
const features =
await this.InstitutionsFeatures.promises.getInstitutionsFeatures(
this.userId
)
expect(features).to.deep.equal(this.testFeatures.features)
})
})
describe('getInstitutionsPlan', function () {
it('should handle error', function (done) {
it('should handle error', async function () {
this.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
return this.InstitutionsFeatures.getInstitutionsPlan(
this.userId,
error => {
expect(error).to.exist
return done()
}
)
await expect(
this.InstitutionsFeatures.promises.getInstitutionsPlan(this.userId)
).to.be.rejected
})
it('should return no plan if user has no licence', function (done) {
it('should return no plan if user has no licence', async function () {
this.UserGetter.promises.getUserFullEmails.resolves(
this.emailDataWithoutLicense
)
return this.InstitutionsFeatures.getInstitutionsPlan(
this.userId,
(error, plan) => {
expect(error).to.not.exist
expect(plan).to.equal(null)
return done()
}
const plan = await this.InstitutionsFeatures.promises.getInstitutionsPlan(
this.userId
)
expect(plan).to.equal(null)
})
it('should return plan if user has licence', function (done) {
it('should return plan if user has licence', async function () {
this.UserGetter.promises.getUserFullEmails.resolves(
this.emailDataWithLicense
)
return this.InstitutionsFeatures.getInstitutionsPlan(
this.userId,
(error, plan) => {
expect(error).to.not.exist
expect(plan).to.equal(this.institutionPlanCode)
return done()
}
const plan = await this.InstitutionsFeatures.promises.getInstitutionsPlan(
this.userId
)
expect(plan).to.equal(this.institutionPlanCode)
})
})
})

View File

@@ -22,22 +22,19 @@ describe('NotificationsBuilder', function () {
})
})
describe('dropboxUnlinkedDueToLapsedReconfirmation', function (done) {
it('should create the notification', function (done) {
this.controller
describe('dropboxUnlinkedDueToLapsedReconfirmation', function () {
it('should create the notification', async function () {
await this.controller.promises
.dropboxUnlinkedDueToLapsedReconfirmation(userId)
.create(error => {
expect(error).to.not.exist
expect(this.handler.createNotification).to.have.been.calledWith(
userId,
'drobox-unlinked-due-to-lapsed-reconfirmation',
'notification_dropbox_unlinked_due_to_lapsed_reconfirmation',
{},
null,
true
)
done()
})
.create()
expect(this.handler.createNotification).to.have.been.calledWith(
userId,
'drobox-unlinked-due-to-lapsed-reconfirmation',
'notification_dropbox_unlinked_due_to_lapsed_reconfirmation',
{},
null,
true
)
})
describe('NotificationsHandler error', function () {
let anError
@@ -45,19 +42,23 @@ describe('NotificationsBuilder', function () {
anError = new Error('oops')
this.handler.createNotification.yields(anError)
})
it('should return errors from NotificationsHandler', function (done) {
this.controller
.dropboxUnlinkedDueToLapsedReconfirmation(userId)
.create(error => {
expect(error).to.exist
expect(error).to.deep.equal(anError)
done()
})
it('should return errors from NotificationsHandler', async function () {
let error
try {
await this.controller.promises
.dropboxUnlinkedDueToLapsedReconfirmation(userId)
.create()
} catch (err) {
error = err
}
expect(error).to.equal(anError)
})
})
})
describe('groupInvitation', function (done) {
describe('groupInvitation', function () {
const subscriptionId = '123123bcabca'
beforeEach(function () {
this.invite = {
@@ -67,29 +68,26 @@ describe('NotificationsBuilder', function () {
}
})
it('should create the notification', function (done) {
this.controller
it('should create the notification', async function () {
await this.controller.promises
.groupInvitation(
userId,
subscriptionId,
this.invite.managedUsersEnabled
)
.create(this.invite, error => {
expect(error).to.not.exist
expect(this.handler.createNotification).to.have.been.calledWith(
userId,
`groupInvitation-${subscriptionId}-${userId}`,
'notification_group_invitation',
{
token: this.invite.token,
inviterName: this.invite.inviterName,
managedUsersEnabled: this.invite.managedUsersEnabled,
},
null,
true
)
done()
})
.create(this.invite)
expect(this.handler.createNotification).to.have.been.calledWith(
userId,
`groupInvitation-${subscriptionId}-${userId}`,
'notification_group_invitation',
{
token: this.invite.token,
inviterName: this.invite.inviterName,
managedUsersEnabled: this.invite.managedUsersEnabled,
},
null,
true
)
})
})
@@ -107,27 +105,25 @@ describe('NotificationsBuilder', function () {
this.request.callsArgWith(1, null, { statusCode: 200 }, this.body)
})
it('should call v1 and create affiliation notifications', function (done) {
it('should call v1 and create affiliation notifications', async function () {
const ip = '192.168.0.1'
this.controller.ipMatcherAffiliation(userId).create(ip, callback => {
this.request.calledOnce.should.equal(true)
const expectedOpts = {
institutionId: this.body.id,
university_name: this.body.name,
content: this.body.enrolment_ad_html,
ssoEnabled: false,
portalPath: undefined,
}
this.handler.createNotification
.calledWith(
userId,
`ip-matched-affiliation-${this.body.id}`,
'notification_ip_matched_affiliation',
expectedOpts
)
.should.equal(true)
done()
})
await this.controller.promises.ipMatcherAffiliation(userId).create(ip)
this.request.calledOnce.should.equal(true)
const expectedOpts = {
institutionId: this.body.id,
university_name: this.body.name,
content: this.body.enrolment_ad_html,
ssoEnabled: false,
portalPath: undefined,
}
this.handler.createNotification
.calledWith(
userId,
`ip-matched-affiliation-${this.body.id}`,
'notification_ip_matched_affiliation',
expectedOpts
)
.should.equal(true)
})
})
describe('without portal and without SSO', function () {
@@ -143,27 +139,25 @@ describe('NotificationsBuilder', function () {
this.request.callsArgWith(1, null, { statusCode: 200 }, this.body)
})
it('should call v1 and create affiliation notifications', function (done) {
it('should call v1 and create affiliation notifications', async function () {
const ip = '192.168.0.1'
this.controller.ipMatcherAffiliation(userId).create(ip, callback => {
this.request.calledOnce.should.equal(true)
const expectedOpts = {
institutionId: this.body.id,
university_name: this.body.name,
content: this.body.enrolment_ad_html,
ssoEnabled: true,
portalPath: '/edu/stanford',
}
this.handler.createNotification
.calledWith(
userId,
`ip-matched-affiliation-${this.body.id}`,
'notification_ip_matched_affiliation',
expectedOpts
)
.should.equal(true)
done()
})
await this.controller.promises.ipMatcherAffiliation(userId).create(ip)
this.request.calledOnce.should.equal(true)
const expectedOpts = {
institutionId: this.body.id,
university_name: this.body.name,
content: this.body.enrolment_ad_html,
ssoEnabled: true,
portalPath: '/edu/stanford',
}
this.handler.createNotification
.calledWith(
userId,
`ip-matched-affiliation-${this.body.id}`,
'notification_ip_matched_affiliation',
expectedOpts
)
.should.equal(true)
})
})
})

View File

@@ -1,16 +1,3 @@
/* eslint-disable
n/handle-callback-err,
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 { assert } = require('chai')
const sinon = require('sinon')
@@ -18,7 +5,6 @@ const modulePath = require('path').join(
__dirname,
'../../../../app/src/Features/Notifications/NotificationsHandler.js'
)
const _ = require('lodash')
describe('NotificationsHandler', function () {
const userId = '123nd3ijdks'
@@ -27,18 +13,18 @@ describe('NotificationsHandler', function () {
beforeEach(function () {
this.request = sinon.stub().callsArgWith(1)
return (this.handler = SandboxedModule.require(modulePath, {
this.handler = SandboxedModule.require(modulePath, {
requires: {
'@overleaf/settings': {
apis: { notifications: { url: notificationUrl } },
},
request: this.request,
},
}))
})
})
describe('getUserNotifications', function () {
it('should get unread notifications', function (done) {
it('should get unread notifications', async function () {
const stubbedNotifications = [{ _id: notificationId, user_id: userId }]
this.request.callsArgWith(
1,
@@ -46,51 +32,42 @@ describe('NotificationsHandler', function () {
{ statusCode: 200 },
stubbedNotifications
)
return this.handler.getUserNotifications(
userId,
(err, unreadNotifications) => {
stubbedNotifications.should.deep.equal(unreadNotifications)
const getOpts = {
uri: `${notificationUrl}/user/${userId}`,
json: true,
timeout: 1000,
method: 'GET',
}
this.request.calledWith(getOpts).should.equal(true)
return done()
}
)
const unreadNotifications =
await this.handler.promises.getUserNotifications(userId)
stubbedNotifications.should.deep.equal(unreadNotifications)
const getOpts = {
uri: `${notificationUrl}/user/${userId}`,
json: true,
timeout: 1000,
method: 'GET',
}
this.request.calledWith(getOpts).should.equal(true)
})
it('should return empty arrays if there are no notifications', function () {
it('should return empty arrays if there are no notifications', async function () {
this.request.callsArgWith(1, null, { statusCode: 200 }, null)
return this.handler.getUserNotifications(
userId,
(err, unreadNotifications) => {
return unreadNotifications.length.should.equal(0)
}
)
const unreadNotifications =
await this.handler.promises.getUserNotifications(userId)
unreadNotifications.length.should.equal(0)
})
})
describe('markAsRead', function () {
beforeEach(function () {
return (this.key = 'some key here')
this.key = 'some key here'
})
it('should send a delete request when a delete has been received to mark a notification', function (done) {
return this.handler.markAsReadWithKey(userId, this.key, () => {
const opts = {
uri: `${notificationUrl}/user/${userId}`,
json: {
key: this.key,
},
timeout: 1000,
method: 'DELETE',
}
this.request.calledWith(opts).should.equal(true)
return done()
})
it('should send a delete request when a delete has been received to mark a notification', async function () {
await this.handler.promises.markAsReadWithKey(userId, this.key)
const opts = {
uri: `${notificationUrl}/user/${userId}`,
json: {
key: this.key,
},
timeout: 1000,
method: 'DELETE',
}
this.request.calledWith(opts).should.equal(true)
})
})
@@ -99,30 +76,27 @@ describe('NotificationsHandler', function () {
this.key = 'some key here'
this.messageOpts = { value: 12344 }
this.templateKey = 'renderThisHtml'
return (this.expiry = null)
this.expiry = null
})
it('should post the message over', function (done) {
return this.handler.createNotification(
it('should post the message over', async function () {
await this.handler.promises.createNotification(
userId,
this.key,
this.templateKey,
this.messageOpts,
this.expiry,
() => {
const args = this.request.args[0][0]
args.uri.should.equal(`${notificationUrl}/user/${userId}`)
args.timeout.should.equal(1000)
const expectedJson = {
key: this.key,
templateKey: this.templateKey,
messageOpts: this.messageOpts,
forceCreate: true,
}
assert.deepEqual(args.json, expectedJson)
return done()
}
this.expiry
)
const args = this.request.args[0][0]
args.uri.should.equal(`${notificationUrl}/user/${userId}`)
args.timeout.should.equal(1000)
const expectedJson = {
key: this.key,
templateKey: this.templateKey,
messageOpts: this.messageOpts,
forceCreate: true,
}
assert.deepEqual(args.json, expectedJson)
})
describe('when expiry date is supplied', function () {
@@ -130,50 +104,46 @@ describe('NotificationsHandler', function () {
this.key = 'some key here'
this.messageOpts = { value: 12344 }
this.templateKey = 'renderThisHtml'
return (this.expiry = new Date())
this.expiry = new Date()
})
it('should post the message over with expiry field', function (done) {
return this.handler.createNotification(
it('should post the message over with expiry field', async function () {
await this.handler.promises.createNotification(
userId,
this.key,
this.templateKey,
this.messageOpts,
this.expiry,
() => {
const args = this.request.args[0][0]
args.uri.should.equal(`${notificationUrl}/user/${userId}`)
args.timeout.should.equal(1000)
const expectedJson = {
key: this.key,
templateKey: this.templateKey,
messageOpts: this.messageOpts,
expires: this.expiry,
forceCreate: true,
}
assert.deepEqual(args.json, expectedJson)
return done()
}
this.expiry
)
const args = this.request.args[0][0]
args.uri.should.equal(`${notificationUrl}/user/${userId}`)
args.timeout.should.equal(1000)
const expectedJson = {
key: this.key,
templateKey: this.templateKey,
messageOpts: this.messageOpts,
expires: this.expiry,
forceCreate: true,
}
assert.deepEqual(args.json, expectedJson)
})
})
})
describe('markAsReadByKeyOnly', function () {
beforeEach(function () {
return (this.key = 'some key here')
this.key = 'some key here'
})
it('should send a delete request when a delete has been received to mark a notification', function (done) {
return this.handler.markAsReadByKeyOnly(this.key, () => {
const opts = {
uri: `${notificationUrl}/key/${this.key}`,
timeout: 1000,
method: 'DELETE',
}
this.request.calledWith(opts).should.equal(true)
return done()
})
it('should send a delete request when a delete has been received to mark a notification', async function () {
await this.handler.promises.markAsReadByKeyOnly(this.key)
const opts = {
uri: `${notificationUrl}/key/${this.key}`,
timeout: 1000,
method: 'DELETE',
}
this.request.calledWith(opts).should.equal(true)
})
})
})

View File

@@ -430,14 +430,15 @@ describe('ProjectEntityHandler', function () {
})
})
it('should call the callback with the lines, version and rev', function (done) {
this.ProjectEntityHandler.getDoc(projectId, docId, doc => {
this.DocstoreManager.promises.getDoc
.calledWith(projectId, docId)
.should.equal(true)
expect(doc).to.exist
done()
})
it('should call the callback with the lines, version and rev', async function () {
const doc = await this.ProjectEntityHandler.promises.getDoc(
projectId,
docId
)
this.DocstoreManager.promises.getDoc
.calledWith(projectId, docId)
.should.equal(true)
expect(doc).to.exist
})
})

View File

@@ -5,7 +5,6 @@ const sinon = require('sinon')
const Errors = require('../../../../app/src/Features/Errors/Errors')
const project = { _id: '1234566', rootFolder: [] }
class Project {}
const rootDoc = { name: 'rootDoc', _id: 'das239djd' }
const doc1 = { name: 'otherDoc.txt', _id: 'dsad2ddd' }
const doc2 = { name: 'docname.txt', _id: 'dsad2ddddd' }
@@ -40,9 +39,6 @@ project.rootDoc_id = rootDoc._id
describe('ProjectLocator', function () {
beforeEach(function () {
Project.findById = (projectId, callback) => {
callback(null, project)
}
this.ProjectGetter = {
getProject: sinon.stub().callsArgWith(2, null, project),
}
@@ -53,7 +49,6 @@ describe('ProjectLocator', function () {
}
this.locator = SandboxedModule.require(modulePath, {
requires: {
'../../models/Project': { Project },
'../../models/User': { User: this.User },
'./ProjectGetter': this.ProjectGetter,
'./ProjectHelper': this.ProjectHelper,
@@ -62,170 +57,152 @@ describe('ProjectLocator', function () {
})
describe('finding a doc', function () {
it('finds one at the root level', function (done) {
this.locator.findElement(
{ project_id: project._id, element_id: doc2._id, type: 'docs' },
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
foundElement._id.should.equal(doc2._id)
path.fileSystem.should.equal(`/${doc2.name}`)
parentFolder._id.should.equal(project.rootFolder[0]._id)
path.mongo.should.equal('rootFolder.0.docs.1')
done()
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',
}
)
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', function (done) {
this.locator.findElement(
{ project_id: project._id, element_id: subSubDoc._id, type: 'doc' },
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
expect(foundElement._id).to.equal(subSubDoc._id)
path.fileSystem.should.equal(
`/${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
)
parentFolder._id.should.equal(secondSubFolder._id)
path.mongo.should.equal('rootFolder.0.folders.1.folders.0.docs.0')
done()
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',
}
)
expect(element._id).to.equal(subSubDoc._id)
path.fileSystem.should.equal(
`/${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
)
folder._id.should.equal(secondSubFolder._id)
path.mongo.should.equal('rootFolder.0.folders.1.folders.0.docs.0')
})
it('should give error if element could not be found', function (done) {
this.locator.findElement(
{ project_id: project._id, element_id: 'ddsd432nj42', type: 'docs' },
(err, foundElement, path, parentFolder) => {
expect(err).to.be.instanceOf(Errors.NotFoundError)
expect(err).to.have.property('message', 'entity not found')
done()
}
it('should give error if element could not be found', async function () {
await expect(
this.locator.promises.findElement({
project_id: project._id,
element_id: 'ddsd432nj42',
type: 'docs',
})
)
.to.eventually.be.rejectedWith(Errors.NotFoundError)
.and.eventually.have.property('message', 'entity not found')
})
})
describe('finding a folder', function () {
it('should return root folder when looking for root folder', function (done) {
this.locator.findElement(
{ project_id: project._id, element_id: rootFolder._id, type: 'folder' },
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
foundElement._id.should.equal(rootFolder._id)
done()
}
)
})
it('when at root', function (done) {
this.locator.findElement(
{ project_id: project._id, element_id: subFolder._id, type: 'folder' },
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
foundElement._id.should.equal(subFolder._id)
path.fileSystem.should.equal(`/${subFolder.name}`)
parentFolder._id.should.equal(rootFolder._id)
path.mongo.should.equal('rootFolder.0.folders.1')
done()
}
)
})
it('when deeply nested', function (done) {
this.locator.findElement(
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: secondSubFolder._id,
element_id: rootFolder._id,
type: 'folder',
},
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
foundElement._id.should.equal(secondSubFolder._id)
path.fileSystem.should.equal(
`/${subFolder.name}/${secondSubFolder.name}`
)
parentFolder._id.should.equal(subFolder._id)
path.mongo.should.equal('rootFolder.0.folders.1.folders.0')
done()
}
)
foundElement._id.should.equal(rootFolder._id)
})
it('when at root', async function () {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
project_id: project._id,
element_id: subFolder._id,
type: 'folder',
})
foundElement._id.should.equal(subFolder._id)
path.fileSystem.should.equal(`/${subFolder.name}`)
parentFolder._id.should.equal(rootFolder._id)
path.mongo.should.equal('rootFolder.0.folders.1')
})
it('when deeply nested', async function () {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
project_id: project._id,
element_id: secondSubFolder._id,
type: 'folder',
})
foundElement._id.should.equal(secondSubFolder._id)
path.fileSystem.should.equal(`/${subFolder.name}/${secondSubFolder.name}`)
parentFolder._id.should.equal(subFolder._id)
path.mongo.should.equal('rootFolder.0.folders.1.folders.0')
})
})
describe('finding a file', function () {
it('when at root', function (done) {
this.locator.findElement(
{ project_id: project._id, element_id: file1._id, type: 'fileRefs' },
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
foundElement._id.should.equal(file1._id)
path.fileSystem.should.equal(`/${file1.name}`)
parentFolder._id.should.equal(rootFolder._id)
path.mongo.should.equal('rootFolder.0.fileRefs.0')
done()
}
)
it('when at root', async function () {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
project_id: project._id,
element_id: file1._id,
type: 'fileRefs',
})
foundElement._id.should.equal(file1._id)
path.fileSystem.should.equal(`/${file1.name}`)
parentFolder._id.should.equal(rootFolder._id)
path.mongo.should.equal('rootFolder.0.fileRefs.0')
})
it('when deeply nested', function (done) {
this.locator.findElement(
{
project_id: project._id,
element_id: subSubFile._id,
type: 'fileRefs',
},
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
foundElement._id.should.equal(subSubFile._id)
path.fileSystem.should.equal(
`/${subFolder.name}/${secondSubFolder.name}/${subSubFile.name}`
)
parentFolder._id.should.equal(secondSubFolder._id)
path.mongo.should.equal('rootFolder.0.folders.1.folders.0.fileRefs.0')
done()
}
it('when deeply nested', async function () {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
project_id: project._id,
element_id: subSubFile._id,
type: 'fileRefs',
})
foundElement._id.should.equal(subSubFile._id)
path.fileSystem.should.equal(
`/${subFolder.name}/${secondSubFolder.name}/${subSubFile.name}`
)
parentFolder._id.should.equal(secondSubFolder._id)
path.mongo.should.equal('rootFolder.0.folders.1.folders.0.fileRefs.0')
})
})
describe('finding an element with wrong element type', function () {
it('should add an s onto the element type', function (done) {
this.locator.findElement(
{ project_id: project._id, element_id: subSubDoc._id, type: 'doc' },
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
foundElement._id.should.equal(subSubDoc._id)
done()
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',
}
)
foundElement._id.should.equal(subSubDoc._id)
})
it('should convert file to fileRefs', function (done) {
this.locator.findElement(
{ project_id: project._id, element_id: file1._id, type: 'fileRefs' },
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
foundElement._id.should.equal(file1._id)
done()
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',
}
)
foundElement._id.should.equal(file1._id)
})
})
@@ -242,233 +219,173 @@ describe('ProjectLocator', function () {
_id: '1234566',
rootFolder: [rootFolder2],
}
it('should find doc in project', function (done) {
this.locator.findElement(
{ project: project2, element_id: doc3._id, type: 'docs' },
(err, foundElement, path, parentFolder) => {
if (err != null) {
return done(err)
}
foundElement._id.should.equal(doc3._id)
path.fileSystem.should.equal(`/${doc3.name}`)
parentFolder._id.should.equal(project2.rootFolder[0]._id)
path.mongo.should.equal('rootFolder.0.docs.0')
done()
}
)
it('should find doc in project', async function () {
const {
element: foundElement,
path,
folder: parentFolder,
} = await this.locator.promises.findElement({
project: project2,
element_id: doc3._id,
type: 'docs',
})
foundElement._id.should.equal(doc3._id)
path.fileSystem.should.equal(`/${doc3.name}`)
parentFolder._id.should.equal(project2.rootFolder[0]._id)
path.mongo.should.equal('rootFolder.0.docs.0')
})
})
describe('finding root doc', function () {
it('should return root doc when passed project', function (done) {
this.locator.findRootDoc(project, (err, doc) => {
if (err != null) {
return done(err)
}
doc._id.should.equal(rootDoc._id)
done()
})
it('should return root doc when passed project', async function () {
const { element: doc } = await this.locator.promises.findRootDoc(project)
doc._id.should.equal(rootDoc._id)
})
it('should return root doc when passed project_id', function (done) {
this.locator.findRootDoc(project._id, (err, doc) => {
if (err != null) {
return done(err)
}
doc._id.should.equal(rootDoc._id)
done()
})
it('should return root doc when passed project_id', async function () {
const { element: doc } = await this.locator.promises.findRootDoc(
project._id
)
doc._id.should.equal(rootDoc._id)
})
it('should return null when the project has no rootDoc', function (done) {
it('should return null when the project has no rootDoc', async function () {
project.rootDoc_id = null
this.locator.findRootDoc(project, (err, doc) => {
if (err != null) {
return done(err)
}
expect(doc).to.equal(null)
done()
})
const { element: rootDoc } =
await this.locator.promises.findRootDoc(project)
expect(rootDoc).to.equal(null)
})
it('should return null when the rootDoc_id no longer exists', function (done) {
it('should return null when the rootDoc_id no longer exists', async function () {
project.rootDoc_id = 'doesntexist'
this.locator.findRootDoc(project, (err, doc) => {
if (err != null) {
return done(err)
}
expect(doc).to.equal(null)
done()
})
const { element: rootDoc } =
await this.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', function (done) {
it('should take a doc path and return the element for a root level document', async function () {
const path = `${doc1.name}`
this.locator.findElementByPath(
{ project, path },
(err, element, type, folder) => {
if (err != null) {
return done(err)
}
element.should.deep.equal(doc1)
expect(type).to.equal('doc')
expect(folder).to.equal(rootFolder)
done()
}
)
const { element, type, folder } =
await this.locator.promises.findElementByPath({
project,
path,
})
element.should.deep.equal(doc1)
expect(type).to.equal('doc')
expect(folder).to.equal(rootFolder)
})
it('should take a doc path and return the element for a root level document with a starting slash', function (done) {
it('should take a doc path and return the element for a root level document with a starting slash', async function () {
const path = `/${doc1.name}`
this.locator.findElementByPath(
{ project, path },
(err, element, type, folder) => {
if (err != null) {
return done(err)
}
element.should.deep.equal(doc1)
expect(type).to.equal('doc')
expect(folder).to.equal(rootFolder)
done()
}
)
const { element, type, folder } =
await this.locator.promises.findElementByPath({
project,
path,
})
element.should.deep.equal(doc1)
expect(type).to.equal('doc')
expect(folder).to.equal(rootFolder)
})
it('should take a doc path and return the element for a nested document', function (done) {
it('should take a doc path and return the element for a nested document', async function () {
const path = `${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
this.locator.findElementByPath(
{ project, path },
(err, element, type, folder) => {
if (err != null) {
return done(err)
}
element.should.deep.equal(subSubDoc)
expect(type).to.equal('doc')
expect(folder).to.equal(secondSubFolder)
done()
}
)
const { element, type, folder } =
await this.locator.promises.findElementByPath({
project,
path,
})
element.should.deep.equal(subSubDoc)
expect(type).to.equal('doc')
expect(folder).to.equal(secondSubFolder)
})
it('should take a file path and return the element for a root level document', function (done) {
it('should take a file path and return the element for a root level document', async function () {
const path = `${file1.name}`
this.locator.findElementByPath(
{ project, path },
(err, element, type, folder) => {
if (err != null) {
return done(err)
}
element.should.deep.equal(file1)
expect(type).to.equal('file')
expect(folder).to.equal(rootFolder)
done()
}
)
const { element, type, folder } =
await this.locator.promises.findElementByPath({
project,
path,
})
element.should.deep.equal(file1)
expect(type).to.equal('file')
expect(folder).to.equal(rootFolder)
})
it('should take a file path and return the element for a nested document', function (done) {
it('should take a file path and return the element for a nested document', async function () {
const path = `${subFolder.name}/${secondSubFolder.name}/${subSubFile.name}`
this.locator.findElementByPath(
{ project, path },
(err, element, type, folder) => {
if (err != null) {
return done(err)
}
element.should.deep.equal(subSubFile)
expect(type).to.equal('file')
expect(folder).to.equal(secondSubFolder)
done()
}
)
const { element, type, folder } =
await this.locator.promises.findElementByPath({
project,
path,
})
element.should.deep.equal(subSubFile)
expect(type).to.equal('file')
expect(folder).to.equal(secondSubFolder)
})
it('should take a file path and return the element for a nested document case insenstive', function (done) {
it('should take a file path and return the element for a nested document case insenstive', async function () {
const path = `${subFolder.name.toUpperCase()}/${secondSubFolder.name.toUpperCase()}/${subSubFile.name.toUpperCase()}`
this.locator.findElementByPath(
{ project, path },
(err, element, type, folder) => {
if (err != null) {
return done(err)
}
element.should.deep.equal(subSubFile)
expect(type).to.equal('file')
expect(folder).to.equal(secondSubFolder)
done()
}
)
const { element, type, folder } =
await this.locator.promises.findElementByPath({
project,
path,
})
element.should.deep.equal(subSubFile)
expect(type).to.equal('file')
expect(folder).to.equal(secondSubFolder)
})
it('should not return elements with a case-insensitive match when exactCaseMatch is true', function (done) {
it('should not return elements with a case-insensitive match when exactCaseMatch is true', async function () {
const path = `${subFolder.name.toUpperCase()}/${secondSubFolder.name.toUpperCase()}/${subSubFile.name.toUpperCase()}`
this.locator.findElementByPath(
{ project, path, exactCaseMatch: true },
(err, element, type, folder) => {
err.should.not.equal(undefined)
expect(element).to.be.undefined
expect(type).to.be.undefined
done()
}
)
await expect(
this.locator.promises.findElementByPath({
project,
path,
exactCaseMatch: true,
})
).to.eventually.be.rejected
})
it('should take a file path and return the element for a nested folder', function (done) {
it('should take a file path and return the element for a nested folder', async function () {
const path = `${subFolder.name}/${secondSubFolder.name}`
this.locator.findElementByPath(
{ project, path },
(err, element, type, folder) => {
if (err != null) {
return done(err)
}
element.should.deep.equal(secondSubFolder)
expect(type).to.equal('folder')
expect(folder).to.equal(subFolder)
done()
}
)
const { element, type, folder } =
await this.locator.promises.findElementByPath({
project,
path,
})
element.should.deep.equal(secondSubFolder)
expect(type).to.equal('folder')
expect(folder).to.equal(subFolder)
})
it('should take a file path and return the root folder', function (done) {
it('should take a file path and return the root folder', async function () {
const path = '/'
this.locator.findElementByPath(
{ project, path },
(err, element, type, folder) => {
if (err != null) {
return done(err)
}
element.should.deep.equal(rootFolder)
expect(type).to.equal('folder')
expect(folder).to.equal(null)
done()
}
)
const { element, type, folder } =
await this.locator.promises.findElementByPath({
project,
path,
})
element.should.deep.equal(rootFolder)
expect(type).to.equal('folder')
expect(folder).to.equal(null)
})
it('should return an error if the file can not be found inside know folder', function (done) {
it('should return an error if the file can not be found inside know folder', async function () {
const path = `${subFolder.name}/${secondSubFolder.name}/exist.txt`
this.locator.findElementByPath(
{ project, path },
(err, element, type) => {
err.should.not.equal(undefined)
expect(element).to.be.undefined
expect(type).to.be.undefined
done()
}
)
await expect(this.locator.promises.findElementByPath({ project, path }))
.to.eventually.be.rejected
})
it('should return an error if the file can not be found inside unknown folder', function (done) {
it('should return an error if the file can not be found inside unknown folder', async function () {
const path = 'this/does/not/exist.txt'
this.locator.findElementByPath(
{ project, path },
(err, element, type) => {
err.should.not.equal(undefined)
expect(element).to.be.undefined
expect(type).to.be.undefined
done()
}
)
await expect(
this.locator.promises.findElementByPath({
project,
path,
})
).to.eventually.be.rejected
})
describe('where duplicate folder exists', function () {
@@ -498,18 +415,20 @@ describe('ProjectLocator', function () {
}
})
it('should not call the callback more than once', function (done) {
it('should not call the callback more than once', async function () {
const path = `${this.duplicateFolder.name}/${this.doc.name}`
this.locator.findElementByPath({ project: this.project, path }, () =>
done()
)
await this.locator.promises.findElementByPath({
project: this.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', function (done) {
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`
this.locator.findElementByPath({ project: this.project, path }, () =>
done()
)
await this.locator.promises.findElementByPath({
project: this.project,
path,
})
})
}) // mocha will throw exception if done called multiple times
@@ -526,18 +445,13 @@ describe('ProjectLocator', function () {
}
})
it('should not crash with a null', function (done) {
it('should not crash with a null', async function () {
const path = '/other.tex'
this.locator.findElementByPath(
{ project: this.project, path },
(err, element) => {
if (err != null) {
return done(err)
}
element.name.should.equal('other.tex')
done()
}
)
const { element } = await this.locator.promises.findElementByPath({
project: this.project,
path,
})
element.name.should.equal('other.tex')
})
})
@@ -546,35 +460,31 @@ describe('ProjectLocator', function () {
this.ProjectGetter = { getProject: sinon.stub().callsArg(2) }
})
it('should not crash with a null', function (done) {
it('should not crash with a null', async function () {
const path = '/other.tex'
this.locator.findElementByPath(
{ project_id: project._id, path },
(err, element) => {
expect(err).to.exist
done()
}
)
await expect(
this.locator.promises.findElementByPath({
project_id: project._id,
path,
})
).to.be.rejected
})
})
describe('with a project_id', function () {
it('should take a doc path and return the element for a root level document', function (done) {
it('should take a doc path and return the element for a root level document', async function () {
const path = `${doc1.name}`
this.locator.findElementByPath(
{ project_id: project._id, path },
(err, element, type) => {
if (err != null) {
return done(err)
}
this.ProjectGetter.getProject
.calledWith(project._id, { rootFolder: true, rootDoc_id: true })
.should.equal(true)
element.should.deep.equal(doc1)
expect(type).to.equal('doc')
done()
const { element, type } = await this.locator.promises.findElementByPath(
{
project_id: project._id,
path,
}
)
this.ProjectGetter.getProject
.calledWith(project._id, { rootFolder: true, rootDoc_id: true })
.should.equal(true)
element.should.deep.equal(doc1)
expect(type).to.equal('doc')
})
})
})

View File

@@ -1,16 +1,3 @@
/* eslint-disable
max-len,
no-return-assign,
no-unused-vars,
no-useless-escape,
*/
// 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 { expect } = require('chai')
const { ObjectId } = require('mongodb-legacy')
const sinon = require('sinon')
@@ -33,16 +20,13 @@ describe('ProjectRootDocManager', function () {
this.sl_req_id = 'sl-req-id-123'
this.callback = sinon.stub()
this.globbyFiles = ['a.tex', 'b.tex', 'main.tex']
this.globby = sinon.stub().returns(
new Promise(resolve => {
return resolve(this.globbyFiles)
})
)
this.globby = sinon.stub().resolves(this.globbyFiles)
this.fs = {
readFile: sinon.stub().callsArgWith(2, new Error('file not found')),
stat: sinon.stub().callsArgWith(1, null, { size: 100 }),
}
return (this.ProjectRootDocManager = SandboxedModule.require(modulePath, {
this.ProjectRootDocManager = SandboxedModule.require(modulePath, {
requires: {
'./ProjectEntityHandler': (this.ProjectEntityHandler = {}),
'./ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {}),
@@ -56,7 +40,7 @@ describe('ProjectRootDocManager', function () {
globby: this.globby,
fs: this.fs,
},
}))
})
})
describe('setRootDocAutomatically', function () {
@@ -67,7 +51,7 @@ describe('ProjectRootDocManager', function () {
.returns(true)
})
describe('when there is a suitable root doc', function () {
beforeEach(function (done) {
beforeEach(async function () {
this.docs = {
'/chapter1.tex': {
_id: this.docId1,
@@ -98,27 +82,26 @@ describe('ProjectRootDocManager', function () {
this.ProjectEntityHandler.getAllDocs = sinon
.stub()
.callsArgWith(1, null, this.docs)
this.ProjectRootDocManager.setRootDocAutomatically(
this.project_id,
done
await this.ProjectRootDocManager.promises.setRootDocAutomatically(
this.project_id
)
})
it('should check the docs of the project', function () {
return this.ProjectEntityHandler.getAllDocs
this.ProjectEntityHandler.getAllDocs
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc to the doc containing a documentclass', function () {
return this.ProjectEntityUpdateHandler.setRootDoc
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2)
.should.equal(true)
})
})
describe('when the root doc is an Rtex file', function () {
beforeEach(function (done) {
beforeEach(async function () {
this.docs = {
'/chapter1.tex': {
_id: this.docId1,
@@ -132,21 +115,20 @@ describe('ProjectRootDocManager', function () {
this.ProjectEntityHandler.getAllDocs = sinon
.stub()
.callsArgWith(1, null, this.docs)
return this.ProjectRootDocManager.setRootDocAutomatically(
this.project_id,
done
await this.ProjectRootDocManager.promises.setRootDocAutomatically(
this.project_id
)
})
it('should set the root doc to the doc containing a documentclass', function () {
return this.ProjectEntityUpdateHandler.setRootDoc
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2)
.should.equal(true)
})
})
describe('when there is no suitable root doc', function () {
beforeEach(function (done) {
beforeEach(async function () {
this.docs = {
'/chapter1.tex': {
_id: this.docId1,
@@ -160,16 +142,13 @@ describe('ProjectRootDocManager', function () {
this.ProjectEntityHandler.getAllDocs = sinon
.stub()
.callsArgWith(1, null, this.docs)
return this.ProjectRootDocManager.setRootDocAutomatically(
this.project_id,
done
await this.ProjectRootDocManager.promises.setRootDocAutomatically(
this.project_id
)
})
it('should not set the root doc to the doc containing a documentclass', function () {
return this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(
false
)
this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
})
})
})
@@ -191,13 +170,13 @@ describe('ProjectRootDocManager', function () {
this.fs.readFile
.withArgs('/foo/a/a.tex')
.callsArgWith(2, null, 'Potato? Potahto. Potootee!')
return (this.documentclassContent = '% test\n\\documentclass\n% test')
this.documentclassContent = '% test\n\\documentclass\n% test'
})
describe('when there is a file in a subfolder', function () {
beforeEach(function () {
// have to splice globbyFiles weirdly because of the way the stubbed globby method handles references
return this.globbyFiles.splice(
this.globbyFiles.splice(
0,
this.globbyFiles.length,
'c.tex',
@@ -207,70 +186,56 @@ describe('ProjectRootDocManager', function () {
)
})
it('processes the root folder files first, and then the subfolder, in alphabetical order', function (done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path) => {
expect(error).not.to.exist
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')
)
return done()
}
it('processes the root folder files first, and then the subfolder, in alphabetical order', async function () {
const { path } =
await this.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')
)
})
it('processes smaller files first', function (done) {
it('processes smaller files first', async function () {
this.fs.stat.withArgs('/foo/c.tex').callsArgWith(1, null, { size: 1 })
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path) => {
expect(error).not.to.exist
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')
)
return done()
}
const { path } =
await this.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')
)
})
})
describe('when main.tex contains a documentclass', function () {
beforeEach(function () {
return this.fs.readFile
this.fs.readFile
.withArgs('/foo/main.tex')
.callsArgWith(2, null, this.documentclassContent)
})
it('returns main.tex', function (done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path, content) => {
expect(error).not.to.exist
expect(path).to.equal('main.tex')
expect(content).to.equal(this.documentclassContent)
return done()
}
)
it('returns main.tex', async function () {
const { path, content } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('main.tex')
expect(content).to.equal(this.documentclassContent)
})
it('processes main.text first and stops processing when it finds the content', function (done) {
return 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')
return done()
}
)
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')
})
})
@@ -284,7 +249,7 @@ describe('ProjectRootDocManager', function () {
.callsArgWith(2, null, 'foo')
})
it('returns the first .tex file from the root folder', function (done) {
it('returns the first .tex file from the root folder', async function () {
this.globbyFiles.splice(
0,
this.globbyFiles.length,
@@ -293,18 +258,15 @@ describe('ProjectRootDocManager', function () {
'nested/chapter1a.tex'
)
this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path, content) => {
expect(error).not.to.exist
expect(path).to.equal('a.tex')
expect(content).to.equal('foo')
return done()
}
)
const { path, content } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('a.tex')
expect(content).to.equal('foo')
})
it('returns main.tex file from the root folder', function (done) {
it('returns main.tex file from the root folder', async function () {
this.globbyFiles.splice(
0,
this.globbyFiles.length,
@@ -314,227 +276,206 @@ describe('ProjectRootDocManager', function () {
'nested/chapter1a.tex'
)
this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path, content) => {
expect(error).not.to.exist
expect(path).to.equal('main.tex')
expect(content).to.equal('foo')
return done()
}
)
const { path, content } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('main.tex')
expect(content).to.equal('foo')
})
})
describe('when a.tex contains a documentclass', function () {
beforeEach(function () {
return this.fs.readFile
this.fs.readFile
.withArgs('/foo/a.tex')
.callsArgWith(2, null, this.documentclassContent)
})
it('returns a.tex', function (done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path, content) => {
expect(error).not.to.exist
expect(path).to.equal('a.tex')
expect(content).to.equal(this.documentclassContent)
return done()
}
)
it('returns a.tex', async function () {
const { path, content } =
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
expect(path).to.equal('a.tex')
expect(content).to.equal(this.documentclassContent)
})
it('processes main.text first and stops processing when it finds the content', function (done) {
return this.ProjectRootDocManager.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')
return done()
}
it('processes main.text first and stops processing when it finds the content', async function () {
await this.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')
})
})
describe('when there is no documentclass', function () {
it('returns with no error', function (done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
error => {
expect(error).not.to.exist
return done()
}
it('returns with no error', async function () {
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
})
it('processes all the files', function (done) {
return this.ProjectRootDocManager.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')
return done()
}
it('processes all the files', async function () {
await this.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')
})
})
describe('when there is an error reading a file', function () {
beforeEach(function () {
return this.fs.readFile
this.fs.readFile
.withArgs('/foo/a.tex')
.callsArgWith(2, new Error('something went wrong'))
})
it('returns an error', function (done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path, content) => {
expect(error).to.exist
expect(path).not.to.exist
expect(content).not.to.exist
return done()
}
)
it('returns an error', async function () {
let error
try {
await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
'/foo'
)
} catch (err) {
error = err
}
expect(error).to.exist
})
})
})
describe('setRootDocFromName', function () {
describe('when there is a suitable root doc', function () {
beforeEach(function (done) {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
'/main.tex',
done
'/main.tex'
)
})
it('should check the docs of the project', function () {
return this.ProjectEntityHandler.getAllDocPathsFromProjectById
this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc to main.tex', function () {
return this.ProjectEntityUpdateHandler.setRootDoc
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2.toString())
.should.equal(true)
})
})
describe('when there is a suitable root doc but the leading slash is missing', function () {
beforeEach(function (done) {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
'main.tex',
done
'main.tex'
)
})
it('should check the docs of the project', function () {
return this.ProjectEntityHandler.getAllDocPathsFromProjectById
this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc to main.tex', function () {
return this.ProjectEntityUpdateHandler.setRootDoc
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2.toString())
.should.equal(true)
})
})
describe('when there is a suitable root doc with a basename match', function () {
beforeEach(function (done) {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
'chapter1a.tex',
done
'chapter1a.tex'
)
})
it('should check the docs of the project', function () {
return this.ProjectEntityHandler.getAllDocPathsFromProjectById
this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc using the basename', function () {
return this.ProjectEntityUpdateHandler.setRootDoc
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId3.toString())
.should.equal(true)
})
})
describe('when there is a suitable root doc but the filename is in quotes', function () {
beforeEach(function (done) {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
"'main.tex'",
done
"'main.tex'"
)
})
it('should check the docs of the project', function () {
return this.ProjectEntityHandler.getAllDocPathsFromProjectById
this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc to main.tex', function () {
return this.ProjectEntityUpdateHandler.setRootDoc
this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.docId2.toString())
.should.equal(true)
})
})
describe('when there is no suitable root doc', function () {
beforeEach(function (done) {
beforeEach(async function () {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
await this.ProjectRootDocManager.promises.setRootDocFromName(
this.project_id,
'other.tex',
done
'other.tex'
)
})
it('should not set the root doc', function () {
return this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(
false
)
this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
})
})
})
@@ -545,73 +486,73 @@ describe('ProjectRootDocManager', function () {
this.ProjectGetter.getProject = sinon
.stub()
.callsArgWith(2, null, this.project)
return (this.ProjectRootDocManager.setRootDocAutomatically = sinon
this.ProjectRootDocManager.setRootDocAutomatically = sinon
.stub()
.callsArgWith(1, null))
.callsArgWith(1, null)
})
describe('when the root doc is set', function () {
beforeEach(function () {
this.project.rootDoc_id = this.docId2
return this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
)
})
it('should find the project fetching only the rootDoc_id field', function () {
return this.ProjectGetter.getProject
this.ProjectGetter.getProject
.calledWith(this.project_id, { rootDoc_id: 1 })
.should.equal(true)
})
it('should not try to update the project rootDoc_id', function () {
return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
false
)
})
it('should call the callback', function () {
return this.callback.called.should.equal(true)
this.callback.called.should.equal(true)
})
})
describe('when the root doc is not set', function () {
beforeEach(function () {
return this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
)
})
it('should find the project with only the rootDoc_id field', function () {
return this.ProjectGetter.getProject
this.ProjectGetter.getProject
.calledWith(this.project_id, { rootDoc_id: 1 })
.should.equal(true)
})
it('should update the project rootDoc_id', function () {
return this.ProjectRootDocManager.setRootDocAutomatically
this.ProjectRootDocManager.setRootDocAutomatically
.calledWith(this.project_id)
.should.equal(true)
})
it('should call the callback', function () {
return this.callback.called.should.equal(true)
this.callback.called.should.equal(true)
})
})
describe('when the project does not exist', function () {
beforeEach(function () {
this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
return this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
)
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -645,26 +586,26 @@ describe('ProjectRootDocManager', function () {
this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
.stub()
.callsArgWith(2, null, this.docPaths[this.docId2])
return this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
)
})
it('should find the project without doc lines', function () {
return this.ProjectGetter.getProjectWithoutDocLines
this.ProjectGetter.getProjectWithoutDocLines
.calledWith(this.project_id)
.should.equal(true)
})
it('should not try to update the project rootDoc_id', function () {
return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
false
)
})
it('should call the callback', function () {
return this.callback.called.should.equal(true)
this.callback.called.should.equal(true)
})
})
@@ -674,58 +615,58 @@ describe('ProjectRootDocManager', function () {
this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
.stub()
.callsArgWith(2, null, null)
return this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
)
})
it('should find the project without doc lines', function () {
return this.ProjectGetter.getProjectWithoutDocLines
this.ProjectGetter.getProjectWithoutDocLines
.calledWith(this.project_id)
.should.equal(true)
})
it('should unset the root doc', function () {
return this.ProjectEntityUpdateHandler.unsetRootDoc
this.ProjectEntityUpdateHandler.unsetRootDoc
.calledWith(this.project_id)
.should.equal(true)
})
it('should try to find a new rootDoc', function () {
return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
true
)
})
it('should call the callback', function () {
return this.callback.called.should.equal(true)
this.callback.called.should.equal(true)
})
})
})
describe('when the root doc is not set', function () {
beforeEach(function () {
return this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
)
})
it('should find the project without doc lines', function () {
return this.ProjectGetter.getProjectWithoutDocLines
this.ProjectGetter.getProjectWithoutDocLines
.calledWith(this.project_id)
.should.equal(true)
})
it('should update the project rootDoc_id', function () {
return this.ProjectRootDocManager.setRootDocAutomatically
this.ProjectRootDocManager.setRootDocAutomatically
.calledWith(this.project_id)
.should.equal(true)
})
it('should call the callback', function () {
return this.callback.called.should.equal(true)
this.callback.called.should.equal(true)
})
})
@@ -734,14 +675,14 @@ describe('ProjectRootDocManager', function () {
this.ProjectGetter.getProjectWithoutDocLines = sinon
.stub()
.callsArgWith(1, null, null)
return this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
)
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)

View File

@@ -42,15 +42,10 @@ describe('PublishersGetter', function () {
})
describe('getManagedPublishers', function () {
it('fetches v1 data before returning publisher list', function (done) {
this.PublishersGetter.getManagedPublishers(
this.userId,
(error, publishers) => {
expect(error).to.be.null
publishers.length.should.equal(1)
done()
}
)
it('fetches v1 data before returning publisher list', async function () {
const publishers =
await this.PublishersGetter.promises.getManagedPublishers(this.userId)
expect(publishers.length).to.equal(1)
})
})
})

View File

@@ -25,23 +25,17 @@ describe('LoginRateLimiter', function () {
})
describe('processLoginRequest', function () {
it('should consume points', function (done) {
this.LoginRateLimiter.processLoginRequest(this.email, (err, allow) => {
if (err) {
return done(err)
}
expect(this.rateLimiter.consume).to.have.been.calledWith(this.email)
done()
})
it('should consume points', async function () {
await this.LoginRateLimiter.promises.processLoginRequest(this.email)
expect(this.rateLimiter.consume).to.have.been.calledWith(this.email)
})
describe('when login is allowed', function () {
it('should call pass allow=true', function (done) {
this.LoginRateLimiter.processLoginRequest(this.email, (err, allow) => {
expect(err).to.equal(null)
expect(allow).to.equal(true)
done()
})
it('should call pass allow=true', async function () {
const allow = await this.LoginRateLimiter.promises.processLoginRequest(
this.email
)
expect(allow).to.equal(true)
})
})
@@ -50,12 +44,11 @@ describe('LoginRateLimiter', function () {
this.rateLimiter.consume.rejects({ remainingPoints: 0 })
})
it('should call pass allow=false', function (done) {
this.LoginRateLimiter.processLoginRequest(this.email, (err, allow) => {
expect(err).to.equal(null)
expect(allow).to.equal(false)
done()
})
it('should call pass allow=false', async function () {
const allow = await this.LoginRateLimiter.promises.processLoginRequest(
this.email
)
expect(allow).to.equal(false)
})
})
@@ -64,22 +57,24 @@ describe('LoginRateLimiter', function () {
this.rateLimiter.consume.rejects(new Error('woops'))
})
it('should produce an error', function (done) {
this.LoginRateLimiter.processLoginRequest(this.email, (err, allow) => {
expect(err).to.not.equal(null)
expect(err).to.be.instanceof(Error)
done()
})
it('should produce an error', async function () {
let error
try {
await this.LoginRateLimiter.promises.processLoginRequest(this.email)
} catch (err) {
error = err
}
expect(error).to.exist
})
})
})
describe('recordSuccessfulLogin', function () {
it('should clear the rate limit', function (done) {
this.LoginRateLimiter.recordSuccessfulLogin(this.email, () => {
expect(this.rateLimiter.delete).to.have.been.calledWith(this.email)
done()
})
it('should clear the rate limit', async function () {
await this.LoginRateLimiter.promises.recordSuccessfulLogin(this.email)
expect(this.rateLimiter.delete).to.have.been.calledWith(this.email)
})
})
})

View File

@@ -294,7 +294,7 @@ describe('RecurlyWrapper', function () {
})
describe('updateAccountEmailAddress, with invalid XML', function () {
beforeEach(async function (done) {
beforeEach(async function () {
this.recurlyAccountId = 'account-id-123'
this.newEmail = '\uD800@example.com'
this.apiRequest = sinon
@@ -307,22 +307,24 @@ describe('RecurlyWrapper', function () {
body: fixtures['accounts/104'],
}
})
done()
})
afterEach(function () {
this.RecurlyWrapper.promises.apiRequest.restore()
})
it('should produce an error', function (done) {
this.RecurlyWrapper.promises
.updateAccountEmailAddress(this.recurlyAccountId, this.newEmail)
.catch(error => {
expect(error).to.exist
expect(error.message.startsWith('Invalid character')).to.equal(true)
expect(this.apiRequest.called).to.equal(false)
done()
})
it('should produce an error', async function () {
try {
await this.RecurlyWrapper.promises.updateAccountEmailAddress(
this.recurlyAccountId,
this.newEmail
)
expect(false).to.equal(true) // Fail if we don't have an error
} catch (error) {
expect(error).to.have.property('message')
expect(error.message.startsWith('Invalid character')).to.be.true
expect(this.apiRequest.called).to.equal(false)
}
})
})
@@ -702,19 +704,27 @@ describe('RecurlyWrapper', function () {
})
})
it('should produce an error', function (done) {
this.call().catch(err => {
expect(err).to.be.instanceof(
SubscriptionErrors.RecurlyTransactionError
)
expect(err.info.public.message).to.be.equal(
'Your card must be authenticated with 3D Secure before continuing.'
)
expect(err.info.public.threeDSecureActionTokenId).to.be.equal(
'mock_three_d_secure_action_token'
)
done()
})
it('should produce an error', async function () {
const promise = this.call()
let error
try {
await promise
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(
SubscriptionErrors.RecurlyTransactionError
)
expect(error).to.have.nested.property(
'info.public.message',
'Your card must be authenticated with 3D Secure before continuing.'
)
expect(error).to.have.nested.property(
'info.public.threeDSecureActionTokenId',
'mock_three_d_secure_action_token'
)
})
})

View File

@@ -131,174 +131,149 @@ describe('TeamInvitesHandler', function () {
})
describe('getInvite', function () {
it("returns the invite if there's one", function (done) {
this.TeamInvitesHandler.getInvite(
this.token,
(err, invite, subscription) => {
expect(err).to.eq(null)
expect(invite).to.deep.eq(this.teamInvite)
expect(subscription).to.deep.eq(this.subscription)
done()
}
)
it("returns the invite if there's one", async function () {
const { invite, subscription } =
await this.TeamInvitesHandler.promises.getInvite(this.token)
expect(invite).to.deep.eq(this.teamInvite)
expect(subscription).to.deep.eq(this.subscription)
})
it("returns teamNotFound if there's none", function (done) {
it("returns teamNotFound if there's none", async function () {
this.Subscription.findOne = sinon.stub().resolves(null)
this.TeamInvitesHandler.getInvite(
this.token,
(err, invite, subscription) => {
expect(err).to.be.instanceof(Errors.NotFoundError)
done()
}
)
let error
try {
await this.TeamInvitesHandler.promises.getInvite(this.token)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(Errors.NotFoundError)
})
})
describe('createInvite', function () {
it('adds the team invite to the subscription', function (done) {
this.TeamInvitesHandler.createInvite(
it('adds the team invite to the subscription', async function () {
const invite = await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
'John.Snow@example.com',
(err, invite) => {
expect(err).to.eq(null)
expect(invite.token).to.eq(this.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)
done()
}
'John.Snow@example.com'
)
expect(invite.token).to.eq(this.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)
})
it('sends an email', function (done) {
this.TeamInvitesHandler.createInvite(
it('sends an email', async function () {
await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
'John.Snow@example.com',
(err, invite) => {
this.EmailHandler.promises.sendEmail
.calledWith(
'verifyEmailToJoinTeam',
sinon.match({
to: 'john.snow@example.com',
inviter: this.manager,
acceptInviteUrl: `http://example.com/subscription/invites/${this.newToken}/`,
})
)
.should.equal(true)
done(err)
}
'John.Snow@example.com'
)
this.EmailHandler.promises.sendEmail
.calledWith(
'verifyEmailToJoinTeam',
sinon.match({
to: 'john.snow@example.com',
inviter: this.manager,
acceptInviteUrl: `http://example.com/subscription/invites/${this.newToken}/`,
})
)
.should.equal(true)
})
it('refreshes the existing invite if the email has already been invited', function (done) {
it('refreshes the existing invite if the email has already been invited', async function () {
const originalInvite = Object.assign({}, this.teamInvite)
this.TeamInvitesHandler.createInvite(
const invite = await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
originalInvite.email,
(err, invite) => {
expect(err).to.eq(null)
expect(invite).to.exist
expect(this.subscription.teamInvites.length).to.eq(1)
expect(this.subscription.teamInvites).to.deep.include(invite)
expect(invite.email).to.eq(originalInvite.email)
this.subscription.save.calledOnce.should.eq(true)
done()
}
originalInvite.email
)
expect(invite).to.exist
expect(this.subscription.teamInvites.length).to.eq(1)
expect(this.subscription.teamInvites).to.deep.include(invite)
expect(invite.email).to.eq(originalInvite.email)
this.subscription.save.calledOnce.should.eq(true)
})
it('removes any legacy invite from the subscription', function (done) {
this.TeamInvitesHandler.createInvite(
it('removes any legacy invite from the subscription', async function () {
await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
'John.Snow@example.com',
(err, invite) => {
this.Subscription.updateOne
.calledWith(
{ _id: new ObjectId('55153a8014829a865bbf700d') },
{ $pull: { invited_emails: 'john.snow@example.com' } }
)
.should.eq(true)
done(err)
}
'John.Snow@example.com'
)
this.Subscription.updateOne
.calledWith(
{ _id: new ObjectId('55153a8014829a865bbf700d') },
{ $pull: { invited_emails: 'john.snow@example.com' } }
)
.should.eq(true)
})
it('add user to subscription if inviting self', function (done) {
this.TeamInvitesHandler.createInvite(
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,
(err, invite) => {
sinon.assert.calledWith(
this.SubscriptionUpdater.promises.addUserToGroup,
this.subscription._id,
this.manager._id
)
sinon.assert.notCalled(this.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.invite).to.be.false
done(err)
}
this.manager.email
)
sinon.assert.calledWith(
this.SubscriptionUpdater.promises.addUserToGroup,
this.subscription._id,
this.manager._id
)
sinon.assert.notCalled(this.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.invite).to.be.false
})
it('sends an SSO invite if SSO is enabled and inviting self', function (done) {
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)
.resolves({ enabled: true })
this.TeamInvitesHandler.createInvite(
await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
this.manager.email,
(err, invite) => {
sinon.assert.calledWith(
this.Modules.promises.hooks.fire,
'sendGroupSSOReminder',
this.manager._id,
this.subscription._id
)
done(err)
}
this.manager.email
)
sinon.assert.calledWith(
this.Modules.promises.hooks.fire,
'sendGroupSSOReminder',
this.manager._id,
this.subscription._id
)
})
it('does not send an SSO invite if SSO is disabled and inviting self', function (done) {
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)
.resolves({ enabled: false })
this.TeamInvitesHandler.createInvite(
await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
this.manager.email,
(err, invite) => {
sinon.assert.notCalled(this.Modules.promises.hooks.fire)
done(err)
}
this.manager.email
)
sinon.assert.notCalled(this.Modules.promises.hooks.fire)
})
it('sends a notification if inviting registered user', function (done) {
it('sends a notification if inviting registered user', async function () {
const id = new ObjectId('6a6b3a8014829a865bbf700d')
const managedUsersEnabled = false
@@ -308,22 +283,19 @@ describe('TeamInvitesHandler', function () {
_id: id,
})
this.TeamInvitesHandler.createInvite(
const invite = await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
'John.Snow@example.com',
(err, invite) => {
this.NotificationsBuilder.promises
.groupInvitation(
id.toString(),
this.subscription._id,
managedUsersEnabled
)
.create.calledWith(invite)
.should.eq(true)
done(err)
}
'John.Snow@example.com'
)
this.NotificationsBuilder.promises
.groupInvitation(
id.toString(),
this.subscription._id,
managedUsersEnabled
)
.create.calledWith(invite)
.should.eq(true)
})
})
@@ -375,112 +347,115 @@ describe('TeamInvitesHandler', function () {
})
describe('with standard group', function () {
it('adds the user to the team', function (done) {
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
this.SubscriptionUpdater.promises.addUserToGroup
.calledWith(this.subscription._id, this.user.id)
.should.eq(true)
done()
})
it('adds the user to the team', async function () {
await this.TeamInvitesHandler.promises.acceptInvite(
'dddddddd',
this.user.id
)
this.SubscriptionUpdater.promises.addUserToGroup
.calledWith(this.subscription._id, this.user.id)
.should.eq(true)
})
it('removes the invite from the subscription', function (done) {
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
this.Subscription.updateOne
.calledWith(
{ _id: new ObjectId('55153a8014829a865bbf700d') },
{ $pull: { teamInvites: { email: 'john.snow@example.com' } } }
)
.should.eq(true)
done()
})
it('removes the invite from the subscription', async function () {
await this.TeamInvitesHandler.promises.acceptInvite(
'dddddddd',
this.user.id
)
this.Subscription.updateOne
.calledWith(
{ _id: new ObjectId('55153a8014829a865bbf700d') },
{ $pull: { teamInvites: { email: 'john.snow@example.com' } } }
)
.should.eq(true)
})
it('removes dashboard notification after they accepted group invitation', function (done) {
it('removes dashboard notification after they accepted group invitation', async function () {
const managedUsersEnabled = false
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
sinon.assert.called(
this.NotificationsBuilder.promises.groupInvitation(
this.user.id,
this.subscription._id,
managedUsersEnabled
).read
)
done()
})
await this.TeamInvitesHandler.promises.acceptInvite(
'dddddddd',
this.user.id
)
sinon.assert.called(
this.NotificationsBuilder.promises.groupInvitation(
this.user.id,
this.subscription._id,
managedUsersEnabled
).read
)
})
it('should not schedule an SSO invite reminder', function (done) {
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
sinon.assert.notCalled(this.Modules.promises.hooks.fire)
done()
})
it('should not schedule an SSO invite reminder', async function () {
await this.TeamInvitesHandler.promises.acceptInvite(
'dddddddd',
this.user.id
)
sinon.assert.notCalled(this.Modules.promises.hooks.fire)
})
})
describe('with managed group', function () {
it('should enroll the group member', function (done) {
it('should enroll the group member', async function () {
this.subscription.managedUsersEnabled = true
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
sinon.assert.calledWith(
this.Modules.promises.hooks.fire,
'enrollInManagedSubscription',
this.user.id,
this.subscription
)
done()
})
await this.TeamInvitesHandler.promises.acceptInvite(
'dddddddd',
this.user.id
)
sinon.assert.calledWith(
this.Modules.promises.hooks.fire,
'enrollInManagedSubscription',
this.user.id,
this.subscription
)
})
})
describe('with group SSO enabled', function () {
it('should schedule an SSO invite reminder', function (done) {
it('should schedule an SSO invite reminder', async function () {
this.subscription.ssoConfig = 'ssoconfig1'
this.SSOConfig.findById
.withArgs('ssoconfig1')
.resolves({ enabled: true })
this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
sinon.assert.calledWith(
this.Modules.promises.hooks.fire,
'scheduleGroupSSOReminder',
this.user.id,
this.subscription._id
)
done()
})
await this.TeamInvitesHandler.promises.acceptInvite(
'dddddddd',
this.user.id
)
sinon.assert.calledWith(
this.Modules.promises.hooks.fire,
'scheduleGroupSSOReminder',
this.user.id,
this.subscription._id
)
})
})
})
describe('revokeInvite', function () {
it('removes the team invite from the subscription', function (done) {
this.TeamInvitesHandler.revokeInvite(
it('removes the team invite from the subscription', async function () {
await this.TeamInvitesHandler.promises.revokeInvite(
this.manager._id,
this.subscription,
'jorah@example.com',
() => {
this.Subscription.updateOne
.calledWith(
{ _id: new ObjectId('55153a8014829a865bbf700d') },
{ $pull: { teamInvites: { email: 'jorah@example.com' } } }
)
.should.eq(true)
this.Subscription.updateOne
.calledWith(
{ _id: new ObjectId('55153a8014829a865bbf700d') },
{ $pull: { invited_emails: 'jorah@example.com' } }
)
.should.eq(true)
done()
}
'jorah@example.com'
)
this.Subscription.updateOne
.calledWith(
{ _id: new ObjectId('55153a8014829a865bbf700d') },
{ $pull: { teamInvites: { email: 'jorah@example.com' } } }
)
.should.eq(true)
this.Subscription.updateOne
.calledWith(
{ _id: new ObjectId('55153a8014829a865bbf700d') },
{ $pull: { invited_emails: 'jorah@example.com' } }
)
.should.eq(true)
})
it('removes dashboard notification for pending group invitation', function (done) {
it('removes dashboard notification for pending group invitation', async function () {
const managedUsersEnabled = false
const pendingUser = {
@@ -492,26 +467,23 @@ describe('TeamInvitesHandler', function () {
.withArgs(pendingUser.email)
.resolves(pendingUser)
this.TeamInvitesHandler.revokeInvite(
await this.TeamInvitesHandler.promises.revokeInvite(
this.manager._id,
this.subscription,
pendingUser.email,
() => {
sinon.assert.called(
this.NotificationsBuilder.promises.groupInvitation(
pendingUser.id,
this.subscription._id,
managedUsersEnabled
).read
)
pendingUser.email
)
done()
}
sinon.assert.called(
this.NotificationsBuilder.promises.groupInvitation(
pendingUser.id,
this.subscription._id,
managedUsersEnabled
).read
)
})
})
describe('createTeamInvitesForLegacyInvitedEmail', function (done) {
describe('createTeamInvitesForLegacyInvitedEmail', function () {
beforeEach(function () {
this.subscription.invited_emails = [
'eddard@example.com',
@@ -523,58 +495,71 @@ describe('TeamInvitesHandler', function () {
.resolves([this.subscription])
})
it('sends an invitation email to addresses in the legacy invited_emails field', function (done) {
this.TeamInvitesHandler.createTeamInvitesForLegacyInvitedEmail(
'eddard@example.com',
(err, invites) => {
expect(err).not.to.exist
expect(invites.length).to.eq(1)
it('sends an invitation email to addresses in the legacy invited_emails field', async function () {
const invites =
await this.TeamInvitesHandler.promises.createTeamInvitesForLegacyInvitedEmail(
'eddard@example.com'
)
const [invite] = invites
expect(invite.token).to.eq(this.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(invites.length).to.eq(1)
done()
}
const [invite] = invites
expect(invite.token).to.eq(this.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)
})
})
describe('validation', function () {
it("doesn't create an invite if the team limit has been reached", function (done) {
it("doesn't create an invite if the team limit has been reached", async function () {
this.LimitationsManager.teamHasReachedMemberLimit = sinon
.stub()
.returns(true)
this.TeamInvitesHandler.createInvite(
this.manager._id,
this.subscription,
'John.Snow@example.com',
(err, invite) => {
expect(err).to.deep.equal({ limitReached: true })
done()
}
)
let error
try {
await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
'John.Snow@example.com'
)
} catch (err) {
error = err
}
expect(error).to.exist
expect(error).to.deep.equal({
limitReached: true,
})
})
it("doesn't create an invite if the subscription is not in a group plan", function (done) {
it("doesn't create an invite if the subscription is not in a group plan", async function () {
this.subscription.groupPlan = false
this.TeamInvitesHandler.createInvite(
this.manager._id,
this.subscription,
'John.Snow@example.com',
(err, invite) => {
expect(err).to.deep.equal({ wrongPlan: true })
done()
}
)
let error
try {
await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
'John.Snow@example.com'
)
} catch (err) {
error = err
}
expect(error).to.exist
expect(error).to.deep.equal({
wrongPlan: true,
})
})
it("doesn't create an invite if the user is already part of the team", function (done) {
it("doesn't create an invite if the user is already part of the team", async function () {
const member = {
id: '1a2b',
_id: '1a2b',
@@ -586,16 +571,23 @@ describe('TeamInvitesHandler', function () {
.withArgs(member.email)
.resolves(member)
this.TeamInvitesHandler.createInvite(
this.manager._id,
this.subscription,
'tyrion@example.com',
(err, invite) => {
expect(err).to.deep.equal({ alreadyInTeam: true })
expect(invite).not.to.exist
done()
}
)
let error
try {
await this.TeamInvitesHandler.promises.createInvite(
this.manager._id,
this.subscription,
'tyrion@example.com'
)
} catch (err) {
error = err
}
expect(error).to.exist
expect(error).to.eql({
alreadyInTeam: true,
})
})
})
})

View File

@@ -30,78 +30,72 @@ describe('TagsHandler', function () {
})
describe('finding users tags', function () {
it('should find all the documents with that user id', function (done) {
it('should find all the documents with that user id', async function () {
const stubbedTags = [{ name: 'tag1' }, { name: 'tag2' }, { name: 'tag3' }]
this.TagMock.expects('find')
.once()
.withArgs({ user_id: this.userId })
.resolves(stubbedTags)
this.TagsHandler.getAllTags(this.userId, (err, result) => {
expect(err).to.not.exist
this.TagMock.verify()
expect(result).to.deep.equal(stubbedTags)
done()
})
const result = await this.TagsHandler.promises.getAllTags(this.userId)
this.TagMock.verify()
expect(result).to.deep.equal(stubbedTags)
})
})
describe('createTag', function () {
describe('when insert succeeds', function () {
it('should call insert in mongo', function (done) {
it('should call insert in mongo', async function () {
this.TagMock.expects('create')
.withArgs(this.tag)
.once()
.resolves(this.tag)
this.TagsHandler.createTag(
const resultTag = await this.TagsHandler.promises.createTag(
this.tag.user_id,
this.tag.name,
this.tag.color,
(err, resultTag) => {
expect(err).to.not.exist
this.TagMock.verify()
expect(resultTag.user_id).to.equal(this.tag.user_id)
expect(resultTag.name).to.equal(this.tag.name)
expect(resultTag.color).to.equal(this.tag.color)
done()
}
this.tag.color
)
this.TagMock.verify()
expect(resultTag.user_id).to.equal(this.tag.user_id)
expect(resultTag.name).to.equal(this.tag.name)
expect(resultTag.color).to.equal(this.tag.color)
})
})
describe('when truncate=true, and tag is too long', function () {
it('should truncate the tag name', function (done) {
it('should truncate the tag name', async function () {
// Expect the tag to end up with this truncated name
this.tag.name = 'a comically long tag that will be truncated intern'
this.TagMock.expects('create')
.withArgs(this.tag)
.once()
.resolves(this.tag)
this.TagsHandler.createTag(
const resultTag = await this.TagsHandler.promises.createTag(
this.tag.user_id,
// Pass this too-long name
'a comically long tag that will be truncated internally and not throw an error',
this.tag.color,
{ truncate: true },
(err, resultTag) => {
expect(err).to.not.exist
expect(resultTag.name).to.equal(this.tag.name)
done()
}
{ truncate: true }
)
expect(resultTag.name).to.equal(this.tag.name)
})
})
describe('when tag is too long', function () {
it('should throw an error', function (done) {
this.TagsHandler.createTag(
this.tag.user_id,
'this is a tag that is very very very very very very long',
undefined,
err => {
expect(err.message).to.equal('Exceeded max tag length')
done()
}
)
it('should throw an error', async function () {
let error
try {
await this.TagsHandler.promises.createTag(
this.tag.user_id,
'this is a tag that is very very very very very very long',
undefined
)
} catch (err) {
error = err
}
expect(error).to.exist
expect(error).to.have.property('message', 'Exceeded max tag length')
})
})
@@ -111,7 +105,7 @@ describe('TagsHandler', function () {
this.duplicateKeyError.code = 11000
})
it('should get tag with findOne and return that tag', function (done) {
it('should get tag with findOne and return that tag', async function () {
this.TagMock.expects('create')
.withArgs(this.tag)
.once()
@@ -120,26 +114,22 @@ describe('TagsHandler', function () {
.withArgs({ user_id: this.tag.user_id, name: this.tag.name })
.once()
.resolves(this.tag)
this.TagsHandler.createTag(
const resultTag = await this.TagsHandler.promises.createTag(
this.tag.user_id,
this.tag.name,
this.tag.color,
(err, resultTag) => {
expect(err).to.not.exist
this.TagMock.verify()
expect(resultTag.user_id).to.equal(this.tag.user_id)
expect(resultTag.name).to.equal(this.tag.name)
expect(resultTag.color).to.equal(this.tag.color)
done()
}
this.tag.color
)
this.TagMock.verify()
expect(resultTag.user_id).to.equal(this.tag.user_id)
expect(resultTag.name).to.equal(this.tag.name)
expect(resultTag.color).to.equal(this.tag.color)
})
})
})
describe('addProjectToTag', function () {
describe('with a valid tag_id', function () {
it('should call update in mongo', function (done) {
it('should call update in mongo', async function () {
this.TagMock.expects('findOneAndUpdate')
.once()
.withArgs(
@@ -147,23 +137,19 @@ describe('TagsHandler', function () {
{ $addToSet: { project_ids: this.projectId } }
)
.resolves()
this.TagsHandler.addProjectToTag(
await this.TagsHandler.promises.addProjectToTag(
this.userId,
this.tagId,
this.projectId,
err => {
expect(err).to.not.exist
this.TagMock.verify()
done()
}
this.projectId
)
this.TagMock.verify()
})
})
})
describe('addProjectsToTag', function () {
describe('with a valid tag_id', function () {
it('should call update in mongo', function (done) {
it('should call update in mongo', async function () {
this.TagMock.expects('findOneAndUpdate')
.once()
.withArgs(
@@ -171,22 +157,18 @@ describe('TagsHandler', function () {
{ $addToSet: { project_ids: { $each: this.projectIds } } }
)
.resolves()
this.TagsHandler.addProjectsToTag(
await this.TagsHandler.promises.addProjectsToTag(
this.userId,
this.tagId,
this.projectIds,
err => {
expect(err).to.not.exist
this.TagMock.verify()
done()
}
this.projectIds
)
this.TagMock.verify()
})
})
})
describe('addProjectToTagName', function () {
it('should call update in mongo', function (done) {
it('should call update in mongo', async function () {
this.TagMock.expects('updateOne')
.once()
.withArgs(
@@ -195,22 +177,18 @@ describe('TagsHandler', function () {
{ upsert: true }
)
.resolves()
this.TagsHandler.addProjectToTagName(
await this.TagsHandler.promises.addProjectToTagName(
this.tag.userId,
this.tag.name,
this.projectId,
err => {
expect(err).to.not.exist
this.TagMock.verify()
done()
}
this.projectId
)
this.TagMock.verify()
})
})
describe('removeProjectFromTag', function () {
describe('with a valid tag_id', function () {
it('should call update in mongo', function (done) {
it('should call update in mongo', async function () {
this.TagMock.expects('updateOne')
.once()
.withArgs(
@@ -223,23 +201,20 @@ describe('TagsHandler', function () {
}
)
.resolves()
this.TagsHandler.removeProjectFromTag(
await this.TagsHandler.promises.removeProjectFromTag(
this.userId,
this.tagId,
this.projectId,
err => {
expect(err).to.not.exist
this.TagMock.verify()
done()
}
this.projectId
)
this.TagMock.verify()
})
})
})
describe('removeProjectsFromTag', function () {
describe('with a valid tag_id', function () {
it('should call update in mongo', function (done) {
it('should call update in mongo', async function () {
this.TagMock.expects('updateOne')
.once()
.withArgs(
@@ -252,22 +227,18 @@ describe('TagsHandler', function () {
}
)
.resolves()
this.TagsHandler.removeProjectsFromTag(
await this.TagsHandler.promises.removeProjectsFromTag(
this.userId,
this.tagId,
this.projectIds,
err => {
expect(err).to.not.exist
this.TagMock.verify()
done()
}
this.projectIds
)
this.TagMock.verify()
})
})
})
describe('removeProjectFromAllTags', function () {
it('should pull the project id from the tag', function (done) {
it('should pull the project id from the tag', async function () {
this.TagMock.expects('updateMany')
.once()
.withArgs(
@@ -279,20 +250,16 @@ describe('TagsHandler', function () {
}
)
.resolves()
this.TagsHandler.removeProjectFromAllTags(
await this.TagsHandler.promises.removeProjectFromAllTags(
this.userId,
this.projectId,
err => {
expect(err).to.not.exist
this.TagMock.verify()
done()
}
this.projectId
)
this.TagMock.verify()
})
})
describe('addProjectToTags', function () {
it('should add the project id to each tag', function (done) {
it('should add the project id to each tag', async function () {
const tagIds = []
this.TagMock.expects('updateMany')
@@ -307,38 +274,31 @@ describe('TagsHandler', function () {
}
)
.resolves()
this.TagsHandler.addProjectToTags(
await this.TagsHandler.promises.addProjectToTags(
this.userId,
tagIds,
this.projectId,
(err, result) => {
expect(err).to.not.exist
this.TagMock.verify()
done()
}
this.projectId
)
this.TagMock.verify()
})
})
describe('deleteTag', function () {
describe('with a valid tag_id', function () {
it('should call remove in mongo', function (done) {
it('should call remove in mongo', async function () {
this.TagMock.expects('deleteOne')
.once()
.withArgs({ _id: this.tagId, user_id: this.userId })
.resolves()
this.TagsHandler.deleteTag(this.userId, this.tagId, err => {
expect(err).to.not.exist
this.TagMock.verify()
done()
})
await this.TagsHandler.promises.deleteTag(this.userId, this.tagId)
this.TagMock.verify()
})
})
})
describe('renameTag', function () {
describe('with a valid tag_id', function () {
it('should call remove in mongo', function (done) {
it('should call remove in mongo', async function () {
this.newName = 'new name'
this.TagMock.expects('updateOne')
.once()
@@ -347,30 +307,31 @@ describe('TagsHandler', function () {
{ $set: { name: this.newName } }
)
.resolves()
this.TagsHandler.renameTag(
await this.TagsHandler.promises.renameTag(
this.userId,
this.tagId,
this.newName,
err => {
expect(err).to.not.exist
this.TagMock.verify()
done()
}
this.newName
)
this.TagMock.verify()
})
})
describe('when tag is too long', function () {
it('should throw an error', function (done) {
this.TagsHandler.renameTag(
this.userId,
this.tagId,
'this is a tag that is very very very very very very long',
err => {
expect(err.message).to.equal('Exceeded max tag length')
done()
}
)
it('should throw an error', async function () {
let error
try {
await this.TagsHandler.promises.renameTag(
this.userId,
this.tagId,
'this is a tag that is very very very very very very long'
)
} catch (err) {
error = err
}
expect(error).to.exist
expect(error).to.have.property('message', 'Exceeded max tag length')
})
})
})

View File

@@ -65,22 +65,25 @@ describe('FileTypeManager', function () {
describe('when it is a directory', function () {
beforeEach(function () {
this.stats.isDirectory.returns(true)
this.FileTypeManager.isDirectory('/some/path', this.callback)
})
it('should return true', function () {
this.callback.should.have.been.calledWith(null, true)
it('should return true', async function () {
const result =
await this.FileTypeManager.promises.isDirectory('/some/path')
expect(result).to.equal(true)
})
})
describe('when it is not a directory', function () {
beforeEach(function () {
this.stats.isDirectory.returns(false)
this.FileTypeManager.isDirectory('/some/path', this.callback)
})
it('should return false', function () {
this.callback.should.have.been.calledWith(null, false)
it('should return false', async function () {
const result =
await this.FileTypeManager.promises.isDirectory('/some/path')
expect(result).to.equal(false)
})
})
})
@@ -113,223 +116,158 @@ describe('FileTypeManager', function () {
'/GNUMakefile',
]
TEXT_FILENAMES.forEach(filename => {
it(`should classify ${filename} as text`, function (done) {
this.FileTypeManager.getType(
it(`should classify ${filename} as text`, async function () {
const { binary } = await this.FileTypeManager.promises.getType(
filename,
'utf8.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(false)
done()
}
null
)
binary.should.equal(false)
})
})
it('should not classify short text files as binary', function (done) {
it('should not classify short text files as binary', async function () {
this.stats.size = 2 * 1024 * 1024 // 2MB
this.FileTypeManager.getType(
const { binary } = await this.FileTypeManager.promises.getType(
'/file.tex',
'text-short.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(false)
done()
}
null
)
binary.should.equal(false)
})
it('should not classify text files just under the size limit as binary', function (done) {
it('should not classify text files just under the size limit as binary', async function () {
this.stats.size = 2 * 1024 * 1024 // 2MB
this.FileTypeManager.getType(
const { binary } = await this.FileTypeManager.promises.getType(
'/file.tex',
'text-smaller.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(false)
done()
}
null
)
binary.should.equal(false)
})
it('should classify text files at the size limit as binary', function (done) {
it('should classify text files at the size limit as binary', async function () {
this.stats.size = 2 * 1024 * 1024 // 2MB
this.FileTypeManager.getType(
const { binary } = await this.FileTypeManager.promises.getType(
'/file.tex',
'text-exact.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(true)
done()
}
null
)
binary.should.equal(true)
})
it('should classify long text files as binary', function (done) {
it('should classify long text files as binary', async function () {
this.stats.size = 2 * 1024 * 1024 // 2MB
this.FileTypeManager.getType(
const { binary } = await this.FileTypeManager.promises.getType(
'/file.tex',
'text-long.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(true)
done()
}
null
)
binary.should.equal(true)
})
it('should classify large text files as binary', function (done) {
it('should classify large text files as binary', async function () {
this.stats.size = 8 * 1024 * 1024 // 8MB
this.FileTypeManager.getType(
const { binary } = await this.FileTypeManager.promises.getType(
'/file.tex',
'utf8.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(true)
done()
}
null
)
binary.should.equal(true)
})
it('should not try to determine the encoding of large files', function (done) {
it('should not try to determine the encoding of large files', async function () {
this.stats.size = 8 * 1024 * 1024 // 8MB
this.FileTypeManager.getType('/file.tex', 'utf8.tex', null, err => {
if (err) {
return done(err)
}
sinon.assert.notCalled(this.isUtf8)
done()
})
})
it('should detect the encoding of a utf8 file', function (done) {
this.FileTypeManager.getType(
await this.FileTypeManager.promises.getType(
'/file.tex',
'utf8.tex',
null,
(err, { binary, encoding }) => {
if (err) {
return done(err)
}
sinon.assert.calledOnce(this.isUtf8)
this.isUtf8.returned(true).should.equal(true)
encoding.should.equal('utf-8')
done()
}
null
)
sinon.assert.notCalled(this.isUtf8)
})
it("should return 'latin1' for non-unicode encodings", function (done) {
this.FileTypeManager.getType(
it('should detect the encoding of a utf8 file', async function () {
const { encoding } = await this.FileTypeManager.promises.getType(
'/file.tex',
'utf8.tex',
null
)
sinon.assert.calledOnce(this.isUtf8)
this.isUtf8.returned(true).should.equal(true)
encoding.should.equal('utf-8')
})
it("should return 'latin1' for non-unicode encodings", async function () {
const { encoding } = await this.FileTypeManager.promises.getType(
'/file.tex',
'latin1.tex',
null,
(err, { binary, encoding }) => {
if (err) {
return done(err)
}
sinon.assert.calledOnce(this.isUtf8)
this.isUtf8.returned(false).should.equal(true)
encoding.should.equal('latin1')
done()
}
null
)
sinon.assert.calledOnce(this.isUtf8)
this.isUtf8.returned(false).should.equal(true)
encoding.should.equal('latin1')
})
it('should classify utf16 with BOM as utf-16', function (done) {
this.FileTypeManager.getType(
it('should classify utf16 with BOM as utf-16', async function () {
const { encoding } = await this.FileTypeManager.promises.getType(
'/file.tex',
'utf16.tex',
null,
(err, { binary, encoding }) => {
if (err) {
return done(err)
}
sinon.assert.calledOnce(this.isUtf8)
this.isUtf8.returned(false).should.equal(true)
encoding.should.equal('utf-16le')
done()
}
null
)
sinon.assert.calledOnce(this.isUtf8)
this.isUtf8.returned(false).should.equal(true)
encoding.should.equal('utf-16le')
})
it('should classify latin1 files with a null char as binary', function (done) {
this.FileTypeManager.getType(
it('should classify latin1 files with a null char as binary', async function () {
const { binary } = await this.FileTypeManager.promises.getType(
'/file.tex',
'latin1-null.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
expect(binary).to.equal(true)
done()
}
null
)
expect(binary).to.equal(true)
})
it('should classify utf8 files with a null char as binary', function (done) {
this.FileTypeManager.getType(
it('should classify utf8 files with a null char as binary', async function () {
const { binary } = await this.FileTypeManager.promises.getType(
'/file.tex',
'utf8-null.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
expect(binary).to.equal(true)
done()
}
null
)
expect(binary).to.equal(true)
})
it('should classify utf8 files with non-BMP chars as binary', function (done) {
this.FileTypeManager.getType(
it('should classify utf8 files with non-BMP chars as binary', async function () {
const { binary } = await this.FileTypeManager.promises.getType(
'/file.tex',
'utf8-non-bmp.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
expect(binary).to.equal(true)
done()
}
null
)
expect(binary).to.equal(true)
})
it('should classify utf8 files with ascii control chars as utf-8', function (done) {
this.FileTypeManager.getType(
'/file.tex',
'utf8-control-chars.tex',
null,
(err, { binary, encoding }) => {
if (err) {
return done(err)
}
expect(binary).to.equal(false)
expect(encoding).to.equal('utf-8')
done()
}
)
it('should classify utf8 files with ascii control chars as utf-8', async function () {
const { binary, encoding } =
await this.FileTypeManager.promises.getType(
'/file.tex',
'utf8-control-chars.tex',
null
)
expect(binary).to.equal(false)
expect(encoding).to.equal('utf-8')
})
})
@@ -342,189 +280,132 @@ describe('FileTypeManager', function () {
'/tex',
]
BINARY_FILENAMES.forEach(filename => {
it(`should classify ${filename} as binary`, function (done) {
this.FileTypeManager.getType(
it(`should classify ${filename} as binary`, async function () {
const { binary } = await this.FileTypeManager.promises.getType(
filename,
'latin1.tex', // even if the content is not binary
null,
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(true)
done()
}
null
)
binary.should.equal(true)
})
})
it('should not try to get the character encoding', function (done) {
this.FileTypeManager.getType('/file.png', 'utf8.tex', null, err => {
if (err) {
return done(err)
}
sinon.assert.notCalled(this.isUtf8)
done()
})
it('should not try to get the character encoding', async function () {
await this.FileTypeManager.promises.getType(
'/file.png',
'utf8.tex',
null
)
sinon.assert.notCalled(this.isUtf8)
})
it('should recognise new binary files as binary', function (done) {
this.FileTypeManager.getType(
it('should recognise new binary files as binary', async function () {
const { binary } = await this.FileTypeManager.promises.getType(
'/file.py',
'latin1.tex',
null,
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(true)
done()
}
null
)
binary.should.equal(true)
})
it('should recognise existing binary files as binary', function (done) {
this.FileTypeManager.getType(
it('should recognise existing binary files as binary', async function () {
const { binary } = await this.FileTypeManager.promises.getType(
'/file.py',
'latin1.tex',
'file',
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(true)
done()
}
'file'
)
binary.should.equal(true)
})
it('should preserve existing non-binary files as non-binary', function (done) {
this.FileTypeManager.getType(
it('should preserve existing non-binary files as non-binary', async function () {
const { binary } = await this.FileTypeManager.promises.getType(
'/file.py',
'latin1.tex',
'doc',
(err, { binary }) => {
if (err) {
return done(err)
}
binary.should.equal(false)
done()
}
'doc'
)
binary.should.equal(false)
})
})
})
describe('shouldIgnore', function () {
it('should ignore tex auxiliary files', function (done) {
this.FileTypeManager.shouldIgnore('file.aux', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(true)
done()
})
it('should ignore tex auxiliary files', async function () {
const ignore =
await this.FileTypeManager.promises.shouldIgnore('file.aux')
ignore.should.equal(true)
})
it('should ignore dotfiles', function (done) {
this.FileTypeManager.shouldIgnore('path/.git', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(true)
done()
})
it('should ignore dotfiles', async function () {
const ignore =
await this.FileTypeManager.promises.shouldIgnore('path/.git')
ignore.should.equal(true)
})
it('should ignore .git directories and contained files', function (done) {
this.FileTypeManager.shouldIgnore('path/.git/info', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(true)
done()
})
it('should ignore .git directories and contained files', async function () {
const ignore =
await this.FileTypeManager.promises.shouldIgnore('path/.git/info')
ignore.should.equal(true)
})
it('should not ignore .latexmkrc dotfile', function (done) {
this.FileTypeManager.shouldIgnore('path/.latexmkrc', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(false)
done()
})
it('should not ignore .latexmkrc dotfile', async function () {
const ignore =
await this.FileTypeManager.promises.shouldIgnore('path/.latexmkrc')
ignore.should.equal(false)
})
it('should ignore __MACOSX', function (done) {
this.FileTypeManager.shouldIgnore('path/__MACOSX', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(true)
done()
})
it('should ignore __MACOSX', async function () {
const ignore =
await this.FileTypeManager.promises.shouldIgnore('path/__MACOSX')
ignore.should.equal(true)
})
it('should ignore synctex files', function (done) {
this.FileTypeManager.shouldIgnore('file.synctex', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(true)
done()
})
it('should ignore synctex files', async function () {
const ignore =
await this.FileTypeManager.promises.shouldIgnore('file.synctex')
ignore.should.equal(true)
})
it('should ignore synctex(busy) files', function (done) {
this.FileTypeManager.shouldIgnore('file.synctex(busy)', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(true)
done()
})
it('should ignore synctex(busy) files', async function () {
const ignore =
await this.FileTypeManager.promises.shouldIgnore('file.synctex(busy)')
ignore.should.equal(true)
})
it('should not ignore .tex files', function (done) {
this.FileTypeManager.shouldIgnore('file.tex', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(false)
done()
})
it('should not ignore .tex files', async function () {
const ignore =
await this.FileTypeManager.promises.shouldIgnore('file.tex')
ignore.should.equal(false)
})
it('should ignore the case of the extension', function (done) {
this.FileTypeManager.shouldIgnore('file.AUX', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(true)
done()
})
it('should ignore the case of the extension', async function () {
const ignore =
await this.FileTypeManager.promises.shouldIgnore('file.AUX')
ignore.should.equal(true)
})
it('should not ignore files with an ignored extension as full name', function (done) {
this.FileTypeManager.shouldIgnore('dvi', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(false)
done()
})
it('should not ignore files with an ignored extension as full name', async function () {
const ignore = await this.FileTypeManager.promises.shouldIgnore('dvi')
ignore.should.equal(false)
})
it('should not ignore directories with an ignored extension as full name', function (done) {
it('should not ignore directories with an ignored extension as full name', async function () {
this.stats.isDirectory.returns(true)
this.FileTypeManager.shouldIgnore('dvi', (err, ignore) => {
if (err) {
return done(err)
}
ignore.should.equal(false)
done()
})
const ignore = await this.FileTypeManager.promises.shouldIgnore('dvi')
ignore.should.equal(false)
})
})
})

View File

@@ -2,6 +2,9 @@ const sinon = require('sinon')
const { expect } = require('chai')
const SandboxedModule = require('sandboxed-module')
const OError = require('@overleaf/o-error')
const {
ThirdPartyUserNotFoundError,
} = require('../../../../app/src/Features/Errors/Errors')
const modulePath =
'../../../../app/src/Features/User/ThirdPartyIdentityManager.js'
@@ -77,16 +80,19 @@ describe('ThirdPartyIdentityManager', function () {
expect(user).to.deep.equal(this.user)
})
})
it('should return ThirdPartyUserNotFoundError when no user linked', function (done) {
this.ThirdPartyIdentityManager.getUser(
'google',
'an-id-not-linked',
(error, user) => {
expect(error).to.exist
expect(error.name).to.equal('ThirdPartyUserNotFoundError')
done()
}
)
it('should return ThirdPartyUserNotFoundError when no user linked', async function () {
let error
try {
await this.ThirdPartyIdentityManager.promises.getUser(
'google',
'an-id-not-linked'
)
} catch (err) {
error = err
}
expect(error).to.be.instanceOf(ThirdPartyUserNotFoundError)
})
})
describe('link', function () {