mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #17935 from overleaf/ar-refactor-compile-async
[web] make CompileManager async GitOrigin-RevId: 617bde1f429fa9aafc7d4bf4ec628b2a22386b19
This commit is contained in:
parent
cee678591f
commit
9c3d9ef590
2 changed files with 344 additions and 401 deletions
|
@ -9,254 +9,219 @@ const ClsiManager = require('./ClsiManager')
|
|||
const Metrics = require('@overleaf/metrics')
|
||||
const { RateLimiter } = require('../../infrastructure/RateLimiter')
|
||||
const UserAnalyticsIdCache = require('../Analytics/UserAnalyticsIdCache')
|
||||
const {
|
||||
callbackify,
|
||||
callbackifyMultiResult,
|
||||
} = require('@overleaf/promise-utils')
|
||||
|
||||
function instrumentWithTimer(fn, key) {
|
||||
return async (...args) => {
|
||||
const timer = new Metrics.Timer(key)
|
||||
try {
|
||||
return await fn(...args)
|
||||
} finally {
|
||||
timer.done()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function compile(projectId, userId, options = {}) {
|
||||
const recentlyCompiled = await CompileManager._checkIfRecentlyCompiled(
|
||||
projectId,
|
||||
userId
|
||||
)
|
||||
if (recentlyCompiled) {
|
||||
return { status: 'too-recently-compiled', outputFiles: [] }
|
||||
}
|
||||
|
||||
try {
|
||||
const canCompile = await CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
options.isAutoCompile,
|
||||
'everyone'
|
||||
)
|
||||
if (!canCompile) {
|
||||
return { status: 'autocompile-backoff', outputFiles: [] }
|
||||
}
|
||||
} catch (error) {
|
||||
return { status: 'autocompile-backoff', outputFiles: [] }
|
||||
}
|
||||
|
||||
await ProjectRootDocManager.promises.ensureRootDocumentIsSet(projectId)
|
||||
|
||||
const limits =
|
||||
await CompileManager.promises.getProjectCompileLimits(projectId)
|
||||
for (const key in limits) {
|
||||
const value = limits[key]
|
||||
options[key] = value
|
||||
}
|
||||
|
||||
try {
|
||||
const canCompile = await CompileManager._checkCompileGroupAutoCompileLimit(
|
||||
options.isAutoCompile,
|
||||
limits.compileGroup
|
||||
)
|
||||
if (!canCompile) {
|
||||
return { status: 'autocompile-backoff', outputFiles: [] }
|
||||
}
|
||||
} catch (error) {
|
||||
return { message: 'autocompile-backoff', outputFiles: [] }
|
||||
}
|
||||
|
||||
// only pass userId down to clsi if this is a per-user compile
|
||||
const compileAsUser = Settings.disablePerUserCompiles ? undefined : userId
|
||||
const {
|
||||
status,
|
||||
outputFiles,
|
||||
clsiServerId,
|
||||
validationProblems,
|
||||
stats,
|
||||
timings,
|
||||
outputUrlPrefix,
|
||||
} = await ClsiManager.promises.sendRequest(projectId, compileAsUser, options)
|
||||
|
||||
return {
|
||||
status,
|
||||
outputFiles,
|
||||
clsiServerId,
|
||||
limits,
|
||||
validationProblems,
|
||||
stats,
|
||||
timings,
|
||||
outputUrlPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
const instrumentedCompile = instrumentWithTimer(compile, 'editor.compile')
|
||||
|
||||
async function getProjectCompileLimits(projectId) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
owner_ref: 1,
|
||||
})
|
||||
|
||||
const owner = await UserGetter.promises.getUser(project.owner_ref, {
|
||||
_id: 1,
|
||||
alphaProgram: 1,
|
||||
analyticsId: 1,
|
||||
betaProgram: 1,
|
||||
features: 1,
|
||||
})
|
||||
|
||||
const ownerFeatures = (owner && owner.features) || {}
|
||||
// put alpha users into their own compile group
|
||||
if (owner && owner.alphaProgram) {
|
||||
ownerFeatures.compileGroup = 'alpha'
|
||||
}
|
||||
const analyticsId = await UserAnalyticsIdCache.get(owner._id)
|
||||
|
||||
const compileGroup =
|
||||
ownerFeatures.compileGroup || Settings.defaultFeatures.compileGroup
|
||||
const limits = {
|
||||
timeout:
|
||||
ownerFeatures.compileTimeout || Settings.defaultFeatures.compileTimeout,
|
||||
compileGroup,
|
||||
compileBackendClass: compileGroup === 'standard' ? 'n2d' : 'c2d',
|
||||
ownerAnalyticsId: analyticsId,
|
||||
}
|
||||
return limits
|
||||
}
|
||||
|
||||
async function wordCount(projectId, userId, file, clsiserverid) {
|
||||
const limits =
|
||||
await CompileManager.promises.getProjectCompileLimits(projectId)
|
||||
return await ClsiManager.promises.wordCount(
|
||||
projectId,
|
||||
userId,
|
||||
file,
|
||||
limits,
|
||||
clsiserverid
|
||||
)
|
||||
}
|
||||
|
||||
async function stopCompile(projectId, userId) {
|
||||
const limits =
|
||||
await CompileManager.promises.getProjectCompileLimits(projectId)
|
||||
|
||||
return await ClsiManager.promises.stopCompile(projectId, userId, limits)
|
||||
}
|
||||
|
||||
async function deleteAuxFiles(projectId, userId, clsiserverid) {
|
||||
const limits =
|
||||
await CompileManager.promises.getProjectCompileLimits(projectId)
|
||||
|
||||
return await ClsiManager.promises.deleteAuxFiles(
|
||||
projectId,
|
||||
userId,
|
||||
limits,
|
||||
clsiserverid
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = CompileManager = {
|
||||
compile(projectId, userId, options = {}, _callback) {
|
||||
const timer = new Metrics.Timer('editor.compile')
|
||||
const callback = function (...args) {
|
||||
timer.done()
|
||||
_callback(...args)
|
||||
}
|
||||
|
||||
CompileManager._checkIfRecentlyCompiled(
|
||||
projectId,
|
||||
userId,
|
||||
function (error, recentlyCompiled) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
if (recentlyCompiled) {
|
||||
return callback(null, 'too-recently-compiled', [])
|
||||
}
|
||||
|
||||
CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
options.isAutoCompile,
|
||||
'everyone',
|
||||
function (err, canCompile) {
|
||||
if (err || !canCompile) {
|
||||
return callback(null, 'autocompile-backoff', [])
|
||||
}
|
||||
|
||||
ProjectRootDocManager.ensureRootDocumentIsSet(
|
||||
projectId,
|
||||
function (error) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
CompileManager.getProjectCompileLimits(
|
||||
projectId,
|
||||
function (error, limits) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
for (const key in limits) {
|
||||
const value = limits[key]
|
||||
options[key] = value
|
||||
}
|
||||
// Put a lower limit on autocompiles for free users, based on compileGroup
|
||||
CompileManager._checkCompileGroupAutoCompileLimit(
|
||||
options.isAutoCompile,
|
||||
limits.compileGroup,
|
||||
function (err, canCompile) {
|
||||
if (err || !canCompile) {
|
||||
return callback(null, 'autocompile-backoff', [])
|
||||
}
|
||||
// only pass userId down to clsi if this is a per-user compile
|
||||
const compileAsUser = Settings.disablePerUserCompiles
|
||||
? undefined
|
||||
: userId
|
||||
ClsiManager.sendRequest(
|
||||
projectId,
|
||||
compileAsUser,
|
||||
options,
|
||||
function (
|
||||
error,
|
||||
status,
|
||||
outputFiles,
|
||||
clsiServerId,
|
||||
validationProblems,
|
||||
stats,
|
||||
timings,
|
||||
outputUrlPrefix
|
||||
) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
callback(
|
||||
null,
|
||||
status,
|
||||
outputFiles,
|
||||
clsiServerId,
|
||||
limits,
|
||||
validationProblems,
|
||||
stats,
|
||||
timings,
|
||||
outputUrlPrefix
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
promises: {
|
||||
compile: instrumentedCompile,
|
||||
deleteAuxFiles,
|
||||
getProjectCompileLimits,
|
||||
stopCompile,
|
||||
wordCount,
|
||||
},
|
||||
compile: callbackifyMultiResult(instrumentedCompile, [
|
||||
'status',
|
||||
'outputFiles',
|
||||
'clsiServerId',
|
||||
'limits',
|
||||
'validationProblems',
|
||||
'stats',
|
||||
'timings',
|
||||
'outputUrlPrefix',
|
||||
]),
|
||||
|
||||
stopCompile(projectId, userId, callback) {
|
||||
CompileManager.getProjectCompileLimits(projectId, function (error, limits) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
ClsiManager.stopCompile(projectId, userId, limits, callback)
|
||||
})
|
||||
},
|
||||
stopCompile: callbackify(stopCompile),
|
||||
|
||||
deleteAuxFiles(projectId, userId, clsiserverid, callback) {
|
||||
CompileManager.getProjectCompileLimits(projectId, function (error, limits) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
ClsiManager.deleteAuxFiles(
|
||||
projectId,
|
||||
userId,
|
||||
limits,
|
||||
clsiserverid,
|
||||
callback
|
||||
)
|
||||
})
|
||||
},
|
||||
deleteAuxFiles: callbackify(deleteAuxFiles),
|
||||
|
||||
getProjectCompileLimits(projectId, callback) {
|
||||
ProjectGetter.getProject(
|
||||
projectId,
|
||||
{ owner_ref: 1 },
|
||||
function (error, project) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
UserGetter.getUser(
|
||||
project.owner_ref,
|
||||
{
|
||||
_id: 1,
|
||||
alphaProgram: 1,
|
||||
analyticsId: 1,
|
||||
betaProgram: 1,
|
||||
features: 1,
|
||||
},
|
||||
function (err, owner) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
const ownerFeatures = (owner && owner.features) || {}
|
||||
// put alpha users into their own compile group
|
||||
if (owner && owner.alphaProgram) {
|
||||
ownerFeatures.compileGroup = 'alpha'
|
||||
}
|
||||
UserAnalyticsIdCache.callbacks.get(
|
||||
owner._id,
|
||||
function (err, analyticsId) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
const compileGroup =
|
||||
ownerFeatures.compileGroup ||
|
||||
Settings.defaultFeatures.compileGroup
|
||||
const limits = {
|
||||
timeout:
|
||||
ownerFeatures.compileTimeout ||
|
||||
Settings.defaultFeatures.compileTimeout,
|
||||
compileGroup,
|
||||
compileBackendClass:
|
||||
compileGroup === 'standard' ? 'n2d' : 'c2d',
|
||||
ownerAnalyticsId: analyticsId,
|
||||
}
|
||||
callback(null, limits)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
getProjectCompileLimits: callbackify(getProjectCompileLimits),
|
||||
|
||||
COMPILE_DELAY: 1, // seconds
|
||||
_checkIfRecentlyCompiled(projectId, userId, callback) {
|
||||
async _checkIfRecentlyCompiled(projectId, userId) {
|
||||
const key = `compile:${projectId}:${userId}`
|
||||
rclient.set(
|
||||
key,
|
||||
true,
|
||||
'EX',
|
||||
this.COMPILE_DELAY,
|
||||
'NX',
|
||||
function (error, ok) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
if (ok === 'OK') {
|
||||
callback(null, false)
|
||||
} else {
|
||||
callback(null, true)
|
||||
}
|
||||
}
|
||||
)
|
||||
const ok = await rclient.set(key, true, 'EX', this.COMPILE_DELAY, 'NX')
|
||||
return ok !== 'OK'
|
||||
},
|
||||
|
||||
_checkCompileGroupAutoCompileLimit(isAutoCompile, compileGroup, callback) {
|
||||
async _checkCompileGroupAutoCompileLimit(isAutoCompile, compileGroup) {
|
||||
if (!isAutoCompile) {
|
||||
return callback(null, true)
|
||||
return true
|
||||
}
|
||||
if (compileGroup === 'standard') {
|
||||
// apply extra limits to the standard compile group
|
||||
CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
return await CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
isAutoCompile,
|
||||
compileGroup,
|
||||
callback
|
||||
compileGroup
|
||||
)
|
||||
} else {
|
||||
Metrics.inc(`auto-compile-${compileGroup}`)
|
||||
callback(null, true)
|
||||
return true
|
||||
}
|
||||
}, // always allow priority group users to compile
|
||||
|
||||
_checkIfAutoCompileLimitHasBeenHit(isAutoCompile, compileGroup, callback) {
|
||||
async _checkIfAutoCompileLimitHasBeenHit(isAutoCompile, compileGroup) {
|
||||
if (!isAutoCompile) {
|
||||
return callback(null, true)
|
||||
return true
|
||||
}
|
||||
Metrics.inc(`auto-compile-${compileGroup}`)
|
||||
const rateLimiter = getAutoCompileRateLimiter(compileGroup)
|
||||
rateLimiter
|
||||
.consume('global', 1, { method: 'global' })
|
||||
.then(() => {
|
||||
callback(null, true)
|
||||
})
|
||||
.catch(() => {
|
||||
// Don't differentiate between errors and rate limits. Silently trigger
|
||||
// the rate limit if there's an error consuming the points.
|
||||
Metrics.inc(`auto-compile-${compileGroup}-limited`)
|
||||
callback(null, false)
|
||||
})
|
||||
try {
|
||||
await rateLimiter.consume('global', 1, { method: 'global' })
|
||||
return true
|
||||
} catch (e) {
|
||||
// Don't differentiate between errors and rate limits. Silently trigger
|
||||
// the rate limit if there's an error consuming the points.
|
||||
Metrics.inc(`auto-compile-${compileGroup}-limited`)
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
wordCount(projectId, userId, file, clsiserverid, callback) {
|
||||
CompileManager.getProjectCompileLimits(projectId, function (error, limits) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
ClsiManager.wordCount(
|
||||
projectId,
|
||||
userId,
|
||||
file,
|
||||
limits,
|
||||
clsiserverid,
|
||||
callback
|
||||
)
|
||||
})
|
||||
},
|
||||
wordCount: callbackify(wordCount),
|
||||
}
|
||||
|
||||
const autoCompileRateLimiters = new Map()
|
||||
|
|
|
@ -29,18 +29,21 @@ describe('CompileManager', function () {
|
|||
rateLimit: { autoCompile: {} },
|
||||
}),
|
||||
'../../infrastructure/RedisWrapper': {
|
||||
client: () => (this.rclient = { auth() {} }),
|
||||
client: () =>
|
||||
(this.rclient = {
|
||||
auth() {},
|
||||
}),
|
||||
},
|
||||
'../Project/ProjectRootDocManager': (this.ProjectRootDocManager = {}),
|
||||
'../Project/ProjectGetter': (this.ProjectGetter = {}),
|
||||
'../User/UserGetter': (this.UserGetter = {}),
|
||||
'./ClsiManager': (this.ClsiManager = {}),
|
||||
'../Project/ProjectRootDocManager': (this.ProjectRootDocManager = {
|
||||
promises: {},
|
||||
}),
|
||||
'../Project/ProjectGetter': (this.ProjectGetter = { promises: {} }),
|
||||
'../User/UserGetter': (this.UserGetter = { promises: {} }),
|
||||
'./ClsiManager': (this.ClsiManager = { promises: {} }),
|
||||
'../../infrastructure/RateLimiter': this.RateLimiter,
|
||||
'@overleaf/metrics': this.Metrics,
|
||||
'../Analytics/UserAnalyticsIdCache': (this.UserAnalyticsIdCache = {
|
||||
callbacks: {
|
||||
get: sinon.stub().yields(null, 'abc'),
|
||||
},
|
||||
get: sinon.stub().resolves('abc'),
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
@ -57,36 +60,42 @@ describe('CompileManager', function () {
|
|||
beforeEach(function () {
|
||||
this.CompileManager._checkIfRecentlyCompiled = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, false)
|
||||
this.ProjectRootDocManager.ensureRootDocumentIsSet = sinon
|
||||
.resolves(false)
|
||||
this.ProjectRootDocManager.promises.ensureRootDocumentIsSet = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null)
|
||||
this.CompileManager.getProjectCompileLimits = sinon
|
||||
.resolves()
|
||||
this.CompileManager.promises.getProjectCompileLimits = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.limits)
|
||||
this.ClsiManager.sendRequest = sinon
|
||||
.stub()
|
||||
.callsArgWith(
|
||||
3,
|
||||
null,
|
||||
(this.status = 'mock-status'),
|
||||
(this.outputFiles = 'mock output files'),
|
||||
(this.output = 'mock output')
|
||||
)
|
||||
.resolves(this.limits)
|
||||
this.ClsiManager.promises.sendRequest = sinon.stub().resolves({
|
||||
status: (this.status = 'mock-status'),
|
||||
outputFiles: (this.outputFiles = []),
|
||||
clsiServerId: (this.output = 'mock output'),
|
||||
})
|
||||
})
|
||||
|
||||
describe('succesfully', function () {
|
||||
beforeEach(function () {
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = (
|
||||
let result
|
||||
beforeEach(async function () {
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
|
||||
isAutoCompile,
|
||||
compileGroup,
|
||||
cb
|
||||
) => cb(null, true)
|
||||
this.CompileManager.compile(
|
||||
compileGroup
|
||||
) => true
|
||||
this.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.resolves(
|
||||
(this.project = { owner_ref: (this.owner_id = 'owner-id-123') })
|
||||
)
|
||||
this.UserGetter.promises.getUser = sinon.stub().resolves(
|
||||
(this.user = {
|
||||
features: { compileTimeout: '20s', compileGroup: 'standard' },
|
||||
analyticsId: 'abc',
|
||||
})
|
||||
)
|
||||
result = await this.CompileManager.promises.compile(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
{},
|
||||
this.callback
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -97,19 +106,19 @@ describe('CompileManager', function () {
|
|||
})
|
||||
|
||||
it('should ensure that the root document is set', function () {
|
||||
this.ProjectRootDocManager.ensureRootDocumentIsSet
|
||||
this.ProjectRootDocManager.promises.ensureRootDocumentIsSet
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should get the project compile limits', function () {
|
||||
this.CompileManager.getProjectCompileLimits
|
||||
this.CompileManager.promises.getProjectCompileLimits
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should run the compile with the compile limits', function () {
|
||||
this.ClsiManager.sendRequest
|
||||
this.ClsiManager.promises.sendRequest
|
||||
.calledWith(this.project_id, this.user_id, {
|
||||
timeout: this.limits.timeout,
|
||||
compileGroup: 'standard',
|
||||
|
@ -117,10 +126,10 @@ describe('CompileManager', function () {
|
|||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback with the output', function () {
|
||||
this.callback
|
||||
.calledWith(null, this.status, this.outputFiles, this.output)
|
||||
.should.equal(true)
|
||||
it('should resolve with the output', function () {
|
||||
expect(result).to.haveOwnProperty('status', this.status)
|
||||
expect(result).to.haveOwnProperty('clsiServerId', this.output)
|
||||
expect(result).to.haveOwnProperty('outputFiles', this.outputFiles)
|
||||
})
|
||||
|
||||
it('should time the compile', function () {
|
||||
|
@ -130,26 +139,24 @@ describe('CompileManager', function () {
|
|||
|
||||
describe('when the project has been recently compiled', function () {
|
||||
it('should return', function (done) {
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = (
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
|
||||
isAutoCompile,
|
||||
compileGroup,
|
||||
cb
|
||||
) => cb(null, true)
|
||||
compileGroup
|
||||
) => true
|
||||
this.CompileManager._checkIfRecentlyCompiled = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, true)
|
||||
this.CompileManager.compile(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
{},
|
||||
(err, status) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
.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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -157,60 +164,51 @@ describe('CompileManager', function () {
|
|||
it('should return', function (done) {
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, false)
|
||||
this.CompileManager.compile(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
{},
|
||||
(err, status) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
status.should.equal('autocompile-backoff')
|
||||
.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))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getProjectCompileLimits', function () {
|
||||
beforeEach(function (done) {
|
||||
beforeEach(async function () {
|
||||
this.features = {
|
||||
compileTimeout: (this.timeout = 42),
|
||||
compileGroup: (this.group = 'priority'),
|
||||
}
|
||||
this.ProjectGetter.getProject = sinon
|
||||
this.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(
|
||||
2,
|
||||
null,
|
||||
.resolves(
|
||||
(this.project = { owner_ref: (this.owner_id = 'owner-id-123') })
|
||||
)
|
||||
this.UserGetter.getUser = sinon
|
||||
this.UserGetter.promises.getUser = sinon
|
||||
.stub()
|
||||
.callsArgWith(
|
||||
2,
|
||||
null,
|
||||
(this.user = { features: this.features, analyticsId: 'abc' })
|
||||
)
|
||||
this.CompileManager.getProjectCompileLimits(
|
||||
this.project_id,
|
||||
(err, res) => {
|
||||
this.callback(err, res)
|
||||
done()
|
||||
}
|
||||
)
|
||||
.resolves((this.user = { features: this.features, analyticsId: 'abc' }))
|
||||
try {
|
||||
const result =
|
||||
await this.CompileManager.promises.getProjectCompileLimits(
|
||||
this.project_id
|
||||
)
|
||||
this.callback(null, result)
|
||||
} catch (error) {
|
||||
this.callback(error)
|
||||
}
|
||||
})
|
||||
|
||||
it('should look up the owner of the project', function () {
|
||||
this.ProjectGetter.getProject
|
||||
this.ProjectGetter.promises.getProject
|
||||
.calledWith(this.project_id, { owner_ref: 1 })
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should look up the owner's features", function () {
|
||||
this.UserGetter.getUser
|
||||
this.UserGetter.promises.getUser
|
||||
.calledWith(this.project.owner_ref, {
|
||||
_id: 1,
|
||||
alphaProgram: 1,
|
||||
|
@ -239,12 +237,12 @@ describe('CompileManager', function () {
|
|||
compileTimeout: 42,
|
||||
compileGroup: 'standard',
|
||||
}
|
||||
this.ProjectGetter.getProject = sinon
|
||||
this.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.yields(null, { owner_ref: 'owner-id-123' })
|
||||
this.UserGetter.getUser = sinon
|
||||
.resolves({ owner_ref: 'owner-id-123' })
|
||||
this.UserGetter.promises.getUser = sinon
|
||||
.stub()
|
||||
.yields(null, { features: this.features, analyticsId: 'abc' })
|
||||
.resolves({ features: this.features, analyticsId: 'abc' })
|
||||
})
|
||||
|
||||
describe('with priority compile', function () {
|
||||
|
@ -265,47 +263,45 @@ describe('CompileManager', function () {
|
|||
})
|
||||
|
||||
describe('deleteAuxFiles', function () {
|
||||
beforeEach(function () {
|
||||
this.CompileManager.getProjectCompileLimits = sinon
|
||||
let result
|
||||
|
||||
beforeEach(async function () {
|
||||
this.CompileManager.promises.getProjectCompileLimits = sinon
|
||||
.stub()
|
||||
.callsArgWith(
|
||||
1,
|
||||
null,
|
||||
(this.limits = { compileGroup: 'mock-compile-group' })
|
||||
)
|
||||
this.ClsiManager.deleteAuxFiles = sinon.stub().callsArg(3)
|
||||
this.CompileManager.deleteAuxFiles(
|
||||
.resolves((this.limits = { compileGroup: 'mock-compile-group' }))
|
||||
this.ClsiManager.promises.deleteAuxFiles = sinon.stub().resolves('test')
|
||||
result = await this.CompileManager.promises.deleteAuxFiles(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
this.callback
|
||||
this.user_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should look up the compile group to use', function () {
|
||||
this.CompileManager.getProjectCompileLimits
|
||||
this.CompileManager.promises.getProjectCompileLimits
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should delete the aux files', function () {
|
||||
this.ClsiManager.deleteAuxFiles
|
||||
this.ClsiManager.promises.deleteAuxFiles
|
||||
.calledWith(this.project_id, this.user_id, this.limits)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback', function () {
|
||||
this.callback.called.should.equal(true)
|
||||
it('should resolve', function () {
|
||||
expect(result).not.to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
describe('_checkIfRecentlyCompiled', function () {
|
||||
describe('when the key exists in redis', function () {
|
||||
beforeEach(function () {
|
||||
this.rclient.set = sinon.stub().callsArgWith(5, null, null)
|
||||
this.CompileManager._checkIfRecentlyCompiled(
|
||||
let result
|
||||
|
||||
beforeEach(async function () {
|
||||
this.rclient.set = sinon.stub().resolves(null)
|
||||
result = await this.CompileManager._checkIfRecentlyCompiled(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
this.callback
|
||||
this.user_id
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -321,18 +317,19 @@ describe('CompileManager', function () {
|
|||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback with true', function () {
|
||||
this.callback.calledWith(null, true).should.equal(true)
|
||||
it('should resolve with true', function () {
|
||||
result.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the key does not exist in redis', function () {
|
||||
beforeEach(function () {
|
||||
this.rclient.set = sinon.stub().callsArgWith(5, null, 'OK')
|
||||
this.CompileManager._checkIfRecentlyCompiled(
|
||||
let result
|
||||
|
||||
beforeEach(async function () {
|
||||
this.rclient.set = sinon.stub().resolves('OK')
|
||||
result = await this.CompileManager._checkIfRecentlyCompiled(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
this.callback
|
||||
this.user_id
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -348,105 +345,86 @@ describe('CompileManager', function () {
|
|||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback with false', function () {
|
||||
this.callback.calledWith(null, false).should.equal(true)
|
||||
it('should resolve with false', function () {
|
||||
result.should.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_checkIfAutoCompileLimitHasBeenHit', function () {
|
||||
it('should be able to compile if it is not an autocompile', function (done) {
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
false,
|
||||
'everyone',
|
||||
(err, canCompile) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
canCompile.should.equal(true)
|
||||
done()
|
||||
}
|
||||
)
|
||||
it('should be able to compile if it is not an autocompile', async function () {
|
||||
const canCompile =
|
||||
await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
false,
|
||||
'everyone'
|
||||
)
|
||||
expect(canCompile).to.equal(true)
|
||||
})
|
||||
|
||||
it('should be able to compile if rate limit has remaining', function (done) {
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone',
|
||||
(err, canCompile) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(this.rateLimiter.consume).to.have.been.calledWith('global')
|
||||
canCompile.should.equal(true)
|
||||
done()
|
||||
}
|
||||
)
|
||||
it('should be able to compile if rate limit has remaining', async function () {
|
||||
const canCompile =
|
||||
await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone'
|
||||
)
|
||||
|
||||
expect(this.rateLimiter.consume).to.have.been.calledWith('global')
|
||||
expect(canCompile).to.equal(true)
|
||||
})
|
||||
|
||||
it('should be not able to compile if rate limit has no remianing', function (done) {
|
||||
it('should be not able to compile if rate limit has no remianing', async function () {
|
||||
this.rateLimiter.consume.rejects({ remainingPoints: 0 })
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone',
|
||||
(err, canCompile) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
canCompile.should.equal(false)
|
||||
done()
|
||||
}
|
||||
)
|
||||
const canCompile =
|
||||
await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone'
|
||||
)
|
||||
|
||||
expect(canCompile).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return false if there is an error in the rate limit', function (done) {
|
||||
it('should return false if there is an error in the rate limit', async function () {
|
||||
this.rateLimiter.consume.rejects(new Error('BOOM!'))
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone',
|
||||
(err, canCompile) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
canCompile.should.equal(false)
|
||||
done()
|
||||
}
|
||||
)
|
||||
const canCompile =
|
||||
await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone'
|
||||
)
|
||||
|
||||
expect(canCompile).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('wordCount', function () {
|
||||
beforeEach(function () {
|
||||
this.CompileManager.getProjectCompileLimits = sinon
|
||||
let result
|
||||
const wordCount = 1
|
||||
|
||||
beforeEach(async function () {
|
||||
this.CompileManager.promises.getProjectCompileLimits = sinon
|
||||
.stub()
|
||||
.callsArgWith(
|
||||
1,
|
||||
null,
|
||||
(this.limits = { compileGroup: 'mock-compile-group' })
|
||||
)
|
||||
this.ClsiManager.wordCount = sinon.stub().callsArg(4)
|
||||
this.CompileManager.wordCount(
|
||||
.resolves((this.limits = { compileGroup: 'mock-compile-group' }))
|
||||
this.ClsiManager.promises.wordCount = sinon.stub().resolves(wordCount)
|
||||
result = await this.CompileManager.promises.wordCount(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
false,
|
||||
this.callback
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should look up the compile group to use', function () {
|
||||
this.CompileManager.getProjectCompileLimits
|
||||
this.CompileManager.promises.getProjectCompileLimits
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call wordCount for project', function () {
|
||||
this.ClsiManager.wordCount
|
||||
this.ClsiManager.promises.wordCount
|
||||
.calledWith(this.project_id, this.user_id, false, this.limits)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback', function () {
|
||||
this.callback.called.should.equal(true)
|
||||
it('should resolve with the wordCount from the ClsiManager', function () {
|
||||
expect(result).to.equal(wordCount)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue