|
|
@@ -1,13 +1,12 @@
|
|
|
-import async from 'async'
|
|
|
import sinon from 'sinon'
|
|
|
import { expect } from 'chai'
|
|
|
import { strict as esmock } from 'esmock'
|
|
|
+import { RequestFailedError } from '@overleaf/fetch-utils'
|
|
|
|
|
|
const MODULE_PATH = '../../../../app/js/WebApiManager.js'
|
|
|
|
|
|
describe('WebApiManager', function () {
|
|
|
beforeEach(async function () {
|
|
|
- this.request = sinon.stub()
|
|
|
this.settings = {
|
|
|
apis: {
|
|
|
web: {
|
|
|
@@ -17,146 +16,137 @@ describe('WebApiManager', function () {
|
|
|
},
|
|
|
},
|
|
|
}
|
|
|
- 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(),
|
|
|
+ promises: {
|
|
|
+ getCachedHistoryId: sinon.stub(),
|
|
|
+ setCachedHistoryId: sinon.stub().resolves(),
|
|
|
+ },
|
|
|
+ }
|
|
|
+ this.FetchUtils = {
|
|
|
+ fetchNothing: sinon.stub().resolves(),
|
|
|
+ fetchJson: sinon.stub(),
|
|
|
+ RequestFailedError,
|
|
|
}
|
|
|
this.WebApiManager = await esmock(MODULE_PATH, {
|
|
|
- requestretry: this.request,
|
|
|
+ '@overleaf/fetch-utils': this.FetchUtils,
|
|
|
'@overleaf/settings': this.settings,
|
|
|
'@overleaf/metrics': this.Metrics,
|
|
|
'../../../../app/js/RedisManager.js': this.RedisManager,
|
|
|
})
|
|
|
+ this.WebApiManager.setRetryTimeoutMs(100)
|
|
|
})
|
|
|
|
|
|
describe('getHistoryId', function () {
|
|
|
describe('when there is no cached value and the web request is successful', function () {
|
|
|
beforeEach(function () {
|
|
|
- this.RedisManager.getCachedHistoryId
|
|
|
+ this.RedisManager.promises.getCachedHistoryId
|
|
|
.withArgs(this.projectId) // first call, no cached value returned
|
|
|
.onCall(0)
|
|
|
- .yields()
|
|
|
- this.RedisManager.getCachedHistoryId
|
|
|
+ .resolves(null)
|
|
|
+ this.RedisManager.promises.getCachedHistoryId
|
|
|
.withArgs(this.projectId) // subsequent calls, return cached value
|
|
|
- .yields(null, this.olProjectId)
|
|
|
- this.RedisManager.getCachedHistoryId
|
|
|
+ .resolves(this.olProjectId)
|
|
|
+ this.RedisManager.promises.getCachedHistoryId
|
|
|
.withArgs('mock-project-id-2') // no cached value for other project
|
|
|
- .yields()
|
|
|
- this.request.yields(
|
|
|
- null,
|
|
|
- { statusCode: 200 },
|
|
|
- { overleaf: { history: { id: this.olProjectId } } }
|
|
|
- )
|
|
|
+ .resolves(null)
|
|
|
+ this.FetchUtils.fetchJson.resolves({
|
|
|
+ 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)
|
|
|
+ it('should only request project details once per project', async function () {
|
|
|
+ for (let i = 0; i < 5; i++) {
|
|
|
+ await this.WebApiManager.promises.getHistoryId(this.projectId)
|
|
|
+ }
|
|
|
+ this.FetchUtils.fetchJson.should.have.been.calledOnce
|
|
|
|
|
|
- this.WebApiManager.getHistoryId('mock-project-id-2', () => {
|
|
|
- this.request.callCount.should.equal(2)
|
|
|
- done()
|
|
|
- })
|
|
|
- }
|
|
|
- )
|
|
|
+ await this.WebApiManager.promises.getHistoryId('mock-project-id-2')
|
|
|
+ this.FetchUtils.fetchJson.should.have.been.calledTwice
|
|
|
})
|
|
|
|
|
|
- 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 cache the history id', async function () {
|
|
|
+ const olProjectId = await this.WebApiManager.promises.getHistoryId(
|
|
|
+ this.projectId
|
|
|
)
|
|
|
+ this.RedisManager.promises.setCachedHistoryId
|
|
|
+ .calledWith(this.projectId, olProjectId)
|
|
|
+ .should.equal(true)
|
|
|
})
|
|
|
|
|
|
- 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()
|
|
|
+ it("should return the project's history id", async function () {
|
|
|
+ const olProjectId = await this.WebApiManager.promises.getHistoryId(
|
|
|
+ this.projectId
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(this.FetchUtils.fetchJson).to.have.been.calledWithMatch(
|
|
|
+ `${this.settings.apis.web.url}/project/${this.projectId}/details`,
|
|
|
+ {
|
|
|
+ basicAuth: {
|
|
|
+ user: this.settings.apis.web.user,
|
|
|
+ password: this.settings.apis.web.pass,
|
|
|
+ },
|
|
|
}
|
|
|
)
|
|
|
+ expect(olProjectId).to.equal(this.olProjectId)
|
|
|
})
|
|
|
})
|
|
|
|
|
|
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)
|
|
|
+ this.FetchUtils.fetchJson.rejects(this.error)
|
|
|
+ this.RedisManager.promises.getCachedHistoryId.resolves(null)
|
|
|
})
|
|
|
|
|
|
- it('should return an error to the callback', function () {
|
|
|
- this.callback.calledWith(this.error).should.equal(true)
|
|
|
+ it('should throw an error', async function () {
|
|
|
+ await expect(
|
|
|
+ this.WebApiManager.promises.getHistoryId(this.projectId)
|
|
|
+ ).to.be.rejectedWith(this.error)
|
|
|
})
|
|
|
})
|
|
|
|
|
|
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)
|
|
|
+ this.FetchUtils.fetchJson.rejects(
|
|
|
+ new RequestFailedError(
|
|
|
+ 'http://some-url',
|
|
|
+ {},
|
|
|
+ { status: 404 },
|
|
|
+ 'Not found'
|
|
|
+ )
|
|
|
+ )
|
|
|
+ this.RedisManager.promises.getCachedHistoryId.resolves(null)
|
|
|
})
|
|
|
|
|
|
- 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)
|
|
|
+ it('should throw an error', async function () {
|
|
|
+ await expect(
|
|
|
+ this.WebApiManager.promises.getHistoryId(this.projectId)
|
|
|
+ ).to.be.rejectedWith('got a 404 from web api')
|
|
|
})
|
|
|
})
|
|
|
|
|
|
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.RedisManager.promises.getCachedHistoryId.resolves(null)
|
|
|
+ this.FetchUtils.fetchJson.rejects(
|
|
|
+ new RequestFailedError(
|
|
|
+ 'http://some-url',
|
|
|
+ {},
|
|
|
+ { status: 500 },
|
|
|
+ 'Error'
|
|
|
+ )
|
|
|
)
|
|
|
- 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)
|
|
|
+ it('should throw an error', async function () {
|
|
|
+ await expect(
|
|
|
+ this.WebApiManager.promises.getHistoryId(this.projectId)
|
|
|
+ ).to.be.rejectedWith(RequestFailedError)
|
|
|
})
|
|
|
})
|
|
|
})
|