2023-01-13 07:42:29 -05:00
|
|
|
import async from 'async'
|
|
|
|
import sinon from 'sinon'
|
|
|
|
import { expect } from 'chai'
|
|
|
|
import { strict as esmock } from 'esmock'
|
|
|
|
|
|
|
|
const MODULE_PATH = '../../../../app/js/WebApiManager.js'
|
|
|
|
|
|
|
|
describe('WebApiManager', function () {
|
|
|
|
beforeEach(async function () {
|
|
|
|
this.request = sinon.stub()
|
|
|
|
this.settings = {
|
|
|
|
apis: {
|
|
|
|
web: {
|
|
|
|
url: 'http://example.com',
|
2024-02-06 04:34:15 -05:00
|
|
|
user: 'overleaf',
|
2023-01-13 07:42:29 -05:00
|
|
|
pass: 'password',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
this.callback = sinon.stub()
|
|
|
|
this.userId = 'mock-user-id'
|
|
|
|
this.projectId = 'mock-project-id'
|
|
|
|
this.project = { features: 'mock-features' }
|
|
|
|
this.olProjectId = 12345
|
|
|
|
this.Metrics = { inc: sinon.stub() }
|
|
|
|
this.RedisManager = {
|
|
|
|
getCachedHistoryId: sinon.stub(),
|
|
|
|
setCachedHistoryId: sinon.stub().yields(),
|
|
|
|
}
|
|
|
|
this.WebApiManager = await esmock(MODULE_PATH, {
|
|
|
|
requestretry: this.request,
|
|
|
|
'@overleaf/settings': this.settings,
|
|
|
|
'@overleaf/metrics': this.Metrics,
|
|
|
|
'../../../../app/js/RedisManager.js': this.RedisManager,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('getHistoryId', function () {
|
|
|
|
describe('when there is no cached value and the web request is successful', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
this.RedisManager.getCachedHistoryId
|
|
|
|
.withArgs(this.projectId) // first call, no cached value returned
|
|
|
|
.onCall(0)
|
|
|
|
.yields()
|
|
|
|
this.RedisManager.getCachedHistoryId
|
|
|
|
.withArgs(this.projectId) // subsequent calls, return cached value
|
|
|
|
.yields(null, this.olProjectId)
|
|
|
|
this.RedisManager.getCachedHistoryId
|
|
|
|
.withArgs('mock-project-id-2') // no cached value for other project
|
|
|
|
.yields()
|
|
|
|
this.request.yields(
|
|
|
|
null,
|
|
|
|
{ statusCode: 200 },
|
|
|
|
{ overleaf: { history: { id: this.olProjectId } } }
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should only request project details once per project', function (done) {
|
|
|
|
async.times(
|
|
|
|
5,
|
|
|
|
(n, cb) => {
|
|
|
|
this.WebApiManager.getHistoryId(this.projectId, cb)
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
this.request.callCount.should.equal(1)
|
|
|
|
|
|
|
|
this.WebApiManager.getHistoryId('mock-project-id-2', () => {
|
|
|
|
this.request.callCount.should.equal(2)
|
|
|
|
done()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should cache the history id', function (done) {
|
|
|
|
this.WebApiManager.getHistoryId(
|
|
|
|
this.projectId,
|
|
|
|
(error, olProjectId) => {
|
|
|
|
if (error) return done(error)
|
|
|
|
this.RedisManager.setCachedHistoryId
|
|
|
|
.calledWith(this.projectId, olProjectId)
|
|
|
|
.should.equal(true)
|
|
|
|
done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should call the callback with the project', function (done) {
|
|
|
|
this.WebApiManager.getHistoryId(
|
|
|
|
this.projectId,
|
|
|
|
(error, olProjectId) => {
|
|
|
|
expect(error).to.be.null
|
|
|
|
expect(
|
|
|
|
this.request.calledWithMatch({
|
|
|
|
method: 'GET',
|
|
|
|
url: `${this.settings.apis.web.url}/project/${this.projectId}/details`,
|
|
|
|
json: true,
|
|
|
|
auth: {
|
|
|
|
user: this.settings.apis.web.user,
|
|
|
|
pass: this.settings.apis.web.pass,
|
|
|
|
sendImmediately: true,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
).to.be.true
|
|
|
|
expect(olProjectId).to.equal(this.olProjectId)
|
|
|
|
done()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('when the web API returns an error', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
this.error = new Error('something went wrong')
|
|
|
|
this.request.yields(this.error)
|
|
|
|
this.RedisManager.getCachedHistoryId.yields()
|
|
|
|
this.WebApiManager.getHistoryId(this.projectId, this.callback)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should return an error to the callback', function () {
|
|
|
|
this.callback.calledWith(this.error).should.equal(true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('when web returns a 404', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
this.request.callsArgWith(1, null, { statusCode: 404 }, '')
|
|
|
|
this.RedisManager.getCachedHistoryId.yields()
|
|
|
|
this.WebApiManager.getHistoryId(this.projectId, this.callback)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should return the callback with an error', function () {
|
|
|
|
this.callback
|
|
|
|
.calledWith(sinon.match.has('message', 'got a 404 from web api'))
|
|
|
|
.should.equal(true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('when web returns a failure error code', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
this.RedisManager.getCachedHistoryId.yields()
|
|
|
|
this.request.callsArgWith(
|
|
|
|
1,
|
|
|
|
null,
|
|
|
|
{ statusCode: 500, attempts: 42 },
|
|
|
|
''
|
|
|
|
)
|
|
|
|
this.WebApiManager.getHistoryId(this.projectId, this.callback)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should return the callback with an error', function () {
|
|
|
|
this.callback
|
|
|
|
.calledWith(
|
|
|
|
sinon.match.has(
|
|
|
|
'message',
|
|
|
|
'web returned a non-success status code: 500 (attempts: 42)'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.should.equal(true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|