mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
Merge pull request #31142 from overleaf/ar-promisify-web-api-manager
[real-time] promisify web api manager GitOrigin-RevId: da2677c05dd7d066b0a625763d4158b28671615e
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -57039,6 +57039,7 @@
|
|||||||
"@overleaf/logger": "*",
|
"@overleaf/logger": "*",
|
||||||
"@overleaf/metrics": "*",
|
"@overleaf/metrics": "*",
|
||||||
"@overleaf/o-error": "*",
|
"@overleaf/o-error": "*",
|
||||||
|
"@overleaf/promise-utils": "*",
|
||||||
"@overleaf/redis-wrapper": "*",
|
"@overleaf/redis-wrapper": "*",
|
||||||
"@overleaf/settings": "*",
|
"@overleaf/settings": "*",
|
||||||
"@overleaf/validation-tools": "*",
|
"@overleaf/validation-tools": "*",
|
||||||
@@ -57052,7 +57053,6 @@
|
|||||||
"express-session": "^1.17.1",
|
"express-session": "^1.17.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"proxy-addr": "^2.0.7",
|
"proxy-addr": "^2.0.7",
|
||||||
"request": "2.88.2",
|
|
||||||
"socket.io": "github:overleaf/socket.io#0.9.19-overleaf-12",
|
"socket.io": "github:overleaf/socket.io#0.9.19-overleaf-12",
|
||||||
"socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5",
|
"socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5",
|
||||||
"zod-validation-error": "^4.0.1"
|
"zod-validation-error": "^4.0.1"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.
|
|||||||
COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
|
COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
|
||||||
COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
|
COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
|
||||||
COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
|
COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
|
||||||
|
COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
|
||||||
COPY libraries/redis-wrapper/package.json /overleaf/libraries/redis-wrapper/package.json
|
COPY libraries/redis-wrapper/package.json /overleaf/libraries/redis-wrapper/package.json
|
||||||
COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
|
COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
|
||||||
COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
|
COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
|
||||||
@@ -28,6 +29,7 @@ COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
|
|||||||
COPY libraries/logger/ /overleaf/libraries/logger/
|
COPY libraries/logger/ /overleaf/libraries/logger/
|
||||||
COPY libraries/metrics/ /overleaf/libraries/metrics/
|
COPY libraries/metrics/ /overleaf/libraries/metrics/
|
||||||
COPY libraries/o-error/ /overleaf/libraries/o-error/
|
COPY libraries/o-error/ /overleaf/libraries/o-error/
|
||||||
|
COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
|
||||||
COPY libraries/redis-wrapper/ /overleaf/libraries/redis-wrapper/
|
COPY libraries/redis-wrapper/ /overleaf/libraries/redis-wrapper/
|
||||||
COPY libraries/settings/ /overleaf/libraries/settings/
|
COPY libraries/settings/ /overleaf/libraries/settings/
|
||||||
COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
|
COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
|
|||||||
$(MONOREPO)/libraries/logger/package.json \
|
$(MONOREPO)/libraries/logger/package.json \
|
||||||
$(MONOREPO)/libraries/metrics/package.json \
|
$(MONOREPO)/libraries/metrics/package.json \
|
||||||
$(MONOREPO)/libraries/o-error/package.json \
|
$(MONOREPO)/libraries/o-error/package.json \
|
||||||
|
$(MONOREPO)/libraries/promise-utils/package.json \
|
||||||
$(MONOREPO)/libraries/redis-wrapper/package.json \
|
$(MONOREPO)/libraries/redis-wrapper/package.json \
|
||||||
$(MONOREPO)/libraries/settings/package.json \
|
$(MONOREPO)/libraries/settings/package.json \
|
||||||
$(MONOREPO)/libraries/validation-tools/package.json \
|
$(MONOREPO)/libraries/validation-tools/package.json \
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import request from 'request'
|
import { callbackifyMultiResult } from '@overleaf/promise-utils'
|
||||||
import OError from '@overleaf/o-error'
|
import OError from '@overleaf/o-error'
|
||||||
import settings from '@overleaf/settings'
|
import settings from '@overleaf/settings'
|
||||||
import logger from '@overleaf/logger'
|
import logger from '@overleaf/logger'
|
||||||
import Errors from './Errors.js'
|
import Errors from './Errors.js'
|
||||||
|
import Path from 'node:path'
|
||||||
|
import { fetchJson, RequestFailedError } from '@overleaf/fetch-utils'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
CodedError,
|
CodedError,
|
||||||
@@ -11,55 +13,62 @@ const {
|
|||||||
WebApiRequestFailedError,
|
WebApiRequestFailedError,
|
||||||
} = Errors
|
} = Errors
|
||||||
|
|
||||||
export default {
|
async function joinProject(projectId, user) {
|
||||||
joinProject(projectId, user, callback) {
|
const userId = user._id
|
||||||
const userId = user._id
|
logger.debug({ projectId, userId }, 'sending join project request to web')
|
||||||
logger.debug({ projectId, userId }, 'sending join project request to web')
|
const url = new URL(settings.apis.web.url)
|
||||||
const url = `${settings.apis.web.url}/project/${projectId}/join`
|
url.pathname = Path.posix.join('project', projectId, 'join')
|
||||||
request.post(
|
let data
|
||||||
{
|
try {
|
||||||
url,
|
data = await fetchJson(url, {
|
||||||
auth: {
|
method: 'POST',
|
||||||
user: settings.apis.web.user,
|
basicAuth: {
|
||||||
pass: settings.apis.web.pass,
|
user: settings.apis.web.user,
|
||||||
sendImmediately: true,
|
password: settings.apis.web.pass,
|
||||||
},
|
|
||||||
json: {
|
|
||||||
userId,
|
|
||||||
anonymousAccessToken: user.anonymousAccessToken,
|
|
||||||
},
|
|
||||||
jar: false,
|
|
||||||
},
|
},
|
||||||
function (error, response, data) {
|
json: {
|
||||||
if (error) {
|
userId,
|
||||||
OError.tag(error, 'join project request failed')
|
anonymousAccessToken: user.anonymousAccessToken,
|
||||||
return callback(error)
|
},
|
||||||
}
|
})
|
||||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
} catch (error) {
|
||||||
if (!(data && data.project)) {
|
if (error instanceof RequestFailedError) {
|
||||||
return callback(new CorruptedJoinProjectResponseError())
|
if (error.response.status === 429) {
|
||||||
}
|
throw new CodedError(
|
||||||
const userMetadata = {
|
'rate-limit hit when joining project',
|
||||||
isRestrictedUser: data.isRestrictedUser,
|
'TooManyRequests'
|
||||||
isTokenMember: data.isTokenMember,
|
)
|
||||||
isInvitedMember: data.isInvitedMember,
|
} else if (error.response.status === 403) {
|
||||||
}
|
throw new NotAuthorizedError()
|
||||||
callback(null, data.project, data.privilegeLevel, userMetadata)
|
} else if (error.response.status === 404) {
|
||||||
} else if (response.statusCode === 429) {
|
throw new CodedError('project not found', 'ProjectNotFound')
|
||||||
callback(
|
|
||||||
new CodedError(
|
|
||||||
'rate-limit hit when joining project',
|
|
||||||
'TooManyRequests'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else if (response.statusCode === 403) {
|
|
||||||
callback(new NotAuthorizedError())
|
|
||||||
} else if (response.statusCode === 404) {
|
|
||||||
callback(new CodedError('project not found', 'ProjectNotFound'))
|
|
||||||
} else {
|
|
||||||
callback(new WebApiRequestFailedError(response.statusCode))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
throw new WebApiRequestFailedError(error.response.status)
|
||||||
|
}
|
||||||
|
throw OError.tag(error, 'join project request failed')
|
||||||
|
}
|
||||||
|
if (!(data && data.project)) {
|
||||||
|
throw new CorruptedJoinProjectResponseError()
|
||||||
|
}
|
||||||
|
const userMetadata = {
|
||||||
|
isRestrictedUser: data.isRestrictedUser,
|
||||||
|
isTokenMember: data.isTokenMember,
|
||||||
|
isInvitedMember: data.isInvitedMember,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
project: data.project,
|
||||||
|
privilegeLevel: data.privilegeLevel,
|
||||||
|
userMetadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
joinProject: callbackifyMultiResult(joinProject, [
|
||||||
|
'project',
|
||||||
|
'privilegeLevel',
|
||||||
|
'userMetadata',
|
||||||
|
]),
|
||||||
|
promises: {
|
||||||
|
joinProject,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"@overleaf/logger": "*",
|
"@overleaf/logger": "*",
|
||||||
"@overleaf/metrics": "*",
|
"@overleaf/metrics": "*",
|
||||||
"@overleaf/o-error": "*",
|
"@overleaf/o-error": "*",
|
||||||
|
"@overleaf/promise-utils": "*",
|
||||||
"@overleaf/redis-wrapper": "*",
|
"@overleaf/redis-wrapper": "*",
|
||||||
"@overleaf/settings": "*",
|
"@overleaf/settings": "*",
|
||||||
"@overleaf/validation-tools": "*",
|
"@overleaf/validation-tools": "*",
|
||||||
@@ -33,7 +34,6 @@
|
|||||||
"express-session": "^1.17.1",
|
"express-session": "^1.17.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"proxy-addr": "^2.0.7",
|
"proxy-addr": "^2.0.7",
|
||||||
"request": "2.88.2",
|
|
||||||
"socket.io": "github:overleaf/socket.io#0.9.19-overleaf-12",
|
"socket.io": "github:overleaf/socket.io#0.9.19-overleaf-12",
|
||||||
"socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5",
|
"socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5",
|
||||||
"zod-validation-error": "^4.0.1"
|
"zod-validation-error": "^4.0.1"
|
||||||
|
|||||||
@@ -10,16 +10,12 @@ import RealTimeClient from './helpers/RealTimeClient.js'
|
|||||||
import FixturesManager from './helpers/FixturesManager.js'
|
import FixturesManager from './helpers/FixturesManager.js'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import async from 'async'
|
import async from 'async'
|
||||||
import request from 'request'
|
import { fetchNothing } from '@overleaf/fetch-utils'
|
||||||
|
|
||||||
const drain = function (rate, callback) {
|
const drain = async function (rate) {
|
||||||
request.post(
|
await fetchNothing(`http://127.0.0.1:3026/drain?rate=${rate}`, {
|
||||||
{
|
method: 'POST',
|
||||||
url: `http://127.0.0.1:3026/drain?rate=${rate}`,
|
})
|
||||||
},
|
|
||||||
(error, response, data) => callback(error, data)
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('DrainManagerTests', function () {
|
describe('DrainManagerTests', function () {
|
||||||
@@ -34,7 +30,7 @@ describe('DrainManagerTests', function () {
|
|||||||
(e, { project_id: projectId, user_id: userId }) => {
|
(e, { project_id: projectId, user_id: userId }) => {
|
||||||
this.project_id = projectId
|
this.project_id = projectId
|
||||||
this.user_id = userId
|
this.user_id = userId
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
@@ -43,23 +39,23 @@ describe('DrainManagerTests', function () {
|
|||||||
before(function (done) {
|
before(function (done) {
|
||||||
// cleanup to speedup reconnecting
|
// cleanup to speedup reconnecting
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
return RealTimeClient.disconnectAllClients(done)
|
RealTimeClient.disconnectAllClients(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
// trigger and check cleanup
|
// trigger and check cleanup
|
||||||
it('should have disconnected all previous clients', function (done) {
|
it('should have disconnected all previous clients', function (done) {
|
||||||
return RealTimeClient.getConnectedClients((error, data) => {
|
RealTimeClient.getConnectedClients((error, data) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return done(error)
|
return done(error)
|
||||||
}
|
}
|
||||||
expect(data.length).to.equal(0)
|
expect(data.length).to.equal(0)
|
||||||
return done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return describe('with two clients in the project', function () {
|
describe('with two clients in the project', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
return async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
cb => {
|
cb => {
|
||||||
this.clientA = RealTimeClient.connect(this.project_id, cb)
|
this.clientA = RealTimeClient.connect(this.project_id, cb)
|
||||||
@@ -73,34 +69,37 @@ describe('DrainManagerTests', function () {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return describe('starting to drain', function () {
|
describe('starting to drain', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
return async.parallel(
|
async.parallel(
|
||||||
[
|
[
|
||||||
cb => {
|
cb => {
|
||||||
return this.clientA.on('reconnectGracefully', cb)
|
this.clientA.on('reconnectGracefully', cb)
|
||||||
},
|
},
|
||||||
cb => {
|
cb => {
|
||||||
return this.clientB.on('reconnectGracefully', cb)
|
this.clientB.on('reconnectGracefully', cb)
|
||||||
},
|
},
|
||||||
|
|
||||||
cb => drain(2, cb),
|
cb =>
|
||||||
|
drain(2)
|
||||||
|
.then(() => cb())
|
||||||
|
.catch(cb),
|
||||||
],
|
],
|
||||||
done
|
done
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function (done) {
|
afterEach(async function () {
|
||||||
return drain(0, done)
|
await drain(0)
|
||||||
}) // reset drain
|
}) // reset drain
|
||||||
|
|
||||||
it('should not timeout', function () {
|
it('should not timeout', function () {
|
||||||
return expect(true).to.equal(true)
|
expect(true).to.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
return it('should not have disconnected', function () {
|
it('should not have disconnected', function () {
|
||||||
expect(this.clientA.socket.connected).to.equal(true)
|
expect(this.clientA.socket.connected).to.equal(true)
|
||||||
return expect(this.clientB.socket.connected).to.equal(true)
|
expect(this.clientB.socket.connected).to.equal(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,32 +6,26 @@
|
|||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
import async from 'async'
|
import async from 'async'
|
||||||
import Request from 'request'
|
import {
|
||||||
|
fetchJson,
|
||||||
|
fetchNothing,
|
||||||
|
RequestFailedError,
|
||||||
|
} from '@overleaf/fetch-utils'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import RealTimeClient from './helpers/RealTimeClient.js'
|
import RealTimeClient from './helpers/RealTimeClient.js'
|
||||||
import FixturesManager from './helpers/FixturesManager.js'
|
import FixturesManager from './helpers/FixturesManager.js'
|
||||||
|
|
||||||
const request = Request.defaults({
|
|
||||||
baseUrl: 'http://127.0.0.1:3026',
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('HttpControllerTests', function () {
|
describe('HttpControllerTests', function () {
|
||||||
describe('without a user', function () {
|
describe('without a user', function () {
|
||||||
it('should return 404 for the client view', function (done) {
|
it('should return 404 for the client view', async function () {
|
||||||
const clientId = 'not-existing'
|
const clientId = 'not-existing'
|
||||||
request.get(
|
try {
|
||||||
{
|
await fetchNothing(`http://127.0.0.1:3026/clients/${clientId}`)
|
||||||
url: `/clients/${clientId}`,
|
expect.fail('request should have failed')
|
||||||
json: true,
|
} catch (error) {
|
||||||
},
|
expect(error).to.be.instanceof(RequestFailedError)
|
||||||
(error, response, data) => {
|
expect(error.response.status).to.equal(404)
|
||||||
if (error) {
|
}
|
||||||
return done(error)
|
|
||||||
}
|
|
||||||
expect(response.statusCode).to.equal(404)
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -75,32 +69,22 @@ describe('HttpControllerTests', function () {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should send a client view', function (done) {
|
it('should send a client view', async function () {
|
||||||
request.get(
|
const data = await fetchJson(
|
||||||
{
|
`http://127.0.0.1:3026/clients/${this.client.socket.sessionid}`
|
||||||
url: `/clients/${this.client.socket.sessionid}`,
|
|
||||||
json: true,
|
|
||||||
},
|
|
||||||
(error, response, data) => {
|
|
||||||
if (error) {
|
|
||||||
return done(error)
|
|
||||||
}
|
|
||||||
expect(response.statusCode).to.equal(200)
|
|
||||||
expect(data.connected_time).to.exist
|
|
||||||
delete data.connected_time
|
|
||||||
// .email is not set in the session
|
|
||||||
delete data.email
|
|
||||||
expect(data).to.deep.equal({
|
|
||||||
client_id: this.client.socket.sessionid,
|
|
||||||
first_name: 'Joe',
|
|
||||||
last_name: 'Bloggs',
|
|
||||||
project_id: this.project_id,
|
|
||||||
user_id: this.user_id,
|
|
||||||
rooms: [this.project_id, this.doc_id],
|
|
||||||
})
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
expect(data.connected_time).to.exist
|
||||||
|
delete data.connected_time
|
||||||
|
// .email is not set in the session
|
||||||
|
delete data.email
|
||||||
|
expect(data).to.deep.equal({
|
||||||
|
client_id: this.client.socket.sessionid,
|
||||||
|
first_name: 'Joe',
|
||||||
|
last_name: 'Bloggs',
|
||||||
|
project_id: this.project_id,
|
||||||
|
user_id: this.user_id,
|
||||||
|
rooms: [this.project_id, this.doc_id],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
import { vi, describe, beforeEach, it } from 'vitest'
|
import { vi, describe, beforeEach, it } from 'vitest'
|
||||||
/* eslint-disable
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
|
import { RequestFailedError } from '@overleaf/fetch-utils'
|
||||||
|
|
||||||
const modulePath = '../../../app/js/WebApiManager.js'
|
const modulePath = '../../../app/js/WebApiManager.js'
|
||||||
|
|
||||||
@@ -21,9 +11,12 @@ describe('WebApiManager', function () {
|
|||||||
ctx.user = { _id: ctx.user_id }
|
ctx.user = { _id: ctx.user_id }
|
||||||
ctx.callback = sinon.stub()
|
ctx.callback = sinon.stub()
|
||||||
|
|
||||||
vi.doMock('request', () => ({
|
ctx.fetchUtils = {
|
||||||
default: (ctx.request = {}),
|
fetchJson: sinon.stub(),
|
||||||
}))
|
RequestFailedError,
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.doMock('@overleaf/fetch-utils', () => ctx.fetchUtils)
|
||||||
|
|
||||||
vi.doMock('@overleaf/settings', () => ({
|
vi.doMock('@overleaf/settings', () => ({
|
||||||
default: (ctx.settings = {
|
default: (ctx.settings = {
|
||||||
@@ -37,10 +30,10 @@ describe('WebApiManager', function () {
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (ctx.WebApiManager = (await import(modulePath)).default)
|
ctx.WebApiManager = (await import(modulePath)).default
|
||||||
})
|
})
|
||||||
|
|
||||||
return describe('joinProject', function () {
|
describe('joinProject', function () {
|
||||||
describe('successfully', function () {
|
describe('successfully', function () {
|
||||||
beforeEach(function (ctx) {
|
beforeEach(function (ctx) {
|
||||||
ctx.response = {
|
ctx.response = {
|
||||||
@@ -50,36 +43,29 @@ describe('WebApiManager', function () {
|
|||||||
isTokenMember: true,
|
isTokenMember: true,
|
||||||
isInvitedMember: true,
|
isInvitedMember: true,
|
||||||
}
|
}
|
||||||
ctx.request.post = sinon
|
ctx.fetchUtils.fetchJson.resolves(ctx.response)
|
||||||
.stub()
|
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user, ctx.callback)
|
||||||
.callsArgWith(1, null, { statusCode: 200 }, ctx.response)
|
|
||||||
return ctx.WebApiManager.joinProject(
|
|
||||||
ctx.project_id,
|
|
||||||
ctx.user,
|
|
||||||
ctx.callback
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should send a request to web to join the project', function (ctx) {
|
it('should send a request to web to join the project', function (ctx) {
|
||||||
return ctx.request.post
|
ctx.fetchUtils.fetchJson.should.have.been.calledWith(
|
||||||
.calledWith({
|
new URL(`/project/${ctx.project_id}/join`, ctx.settings.apis.web.url),
|
||||||
url: `${ctx.settings.apis.web.url}/project/${ctx.project_id}/join`,
|
{
|
||||||
auth: {
|
method: 'POST',
|
||||||
|
basicAuth: {
|
||||||
user: ctx.settings.apis.web.user,
|
user: ctx.settings.apis.web.user,
|
||||||
pass: ctx.settings.apis.web.pass,
|
password: ctx.settings.apis.web.pass,
|
||||||
sendImmediately: true,
|
|
||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
userId: ctx.user_id,
|
userId: ctx.user_id,
|
||||||
anonymousAccessToken: undefined,
|
anonymousAccessToken: undefined,
|
||||||
},
|
},
|
||||||
jar: false,
|
}
|
||||||
})
|
)
|
||||||
.should.equal(true)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return it('should return the project, privilegeLevel, and restricted flag', function (ctx) {
|
it('should return the project, privilegeLevel, and restricted flag', function (ctx) {
|
||||||
return ctx.callback
|
ctx.callback
|
||||||
.calledWith(null, ctx.response.project, ctx.response.privilegeLevel, {
|
.calledWith(null, ctx.response.project, ctx.response.privilegeLevel, {
|
||||||
isRestrictedUser: ctx.response.isRestrictedUser,
|
isRestrictedUser: ctx.response.isRestrictedUser,
|
||||||
isTokenMember: ctx.response.isTokenMember,
|
isTokenMember: ctx.response.isTokenMember,
|
||||||
@@ -104,26 +90,25 @@ describe('WebApiManager', function () {
|
|||||||
isTokenMember: false,
|
isTokenMember: false,
|
||||||
isInvitedMember: false,
|
isInvitedMember: false,
|
||||||
}
|
}
|
||||||
ctx.request.post = sinon
|
ctx.fetchUtils.fetchJson.resolves(ctx.response)
|
||||||
.stub()
|
|
||||||
.yields(null, { statusCode: 200 }, ctx.response)
|
|
||||||
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user, ctx.callback)
|
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user, ctx.callback)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should send a request to web to join the project', function (ctx) {
|
it('should send a request to web to join the project', function (ctx) {
|
||||||
ctx.request.post.should.have.been.calledWith({
|
ctx.fetchUtils.fetchJson.should.have.been.calledWith(
|
||||||
url: `${ctx.settings.apis.web.url}/project/${ctx.project_id}/join`,
|
new URL(`/project/${ctx.project_id}/join`, ctx.settings.apis.web.url),
|
||||||
auth: {
|
{
|
||||||
user: ctx.settings.apis.web.user,
|
method: 'POST',
|
||||||
pass: ctx.settings.apis.web.pass,
|
basicAuth: {
|
||||||
sendImmediately: true,
|
user: ctx.settings.apis.web.user,
|
||||||
},
|
password: ctx.settings.apis.web.pass,
|
||||||
json: {
|
},
|
||||||
userId: ctx.user_id,
|
json: {
|
||||||
anonymousAccessToken: ctx.token,
|
userId: ctx.user_id,
|
||||||
},
|
anonymousAccessToken: ctx.token,
|
||||||
jar: false,
|
},
|
||||||
})
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return the project, privilegeLevel, and restricted flag', function (ctx) {
|
it('should return the project, privilegeLevel, and restricted flag', function (ctx) {
|
||||||
@@ -142,9 +127,13 @@ describe('WebApiManager', function () {
|
|||||||
|
|
||||||
describe('when web replies with a 403', function () {
|
describe('when web replies with a 403', function () {
|
||||||
beforeEach(function (ctx) {
|
beforeEach(function (ctx) {
|
||||||
ctx.request.post = sinon
|
ctx.fetchUtils.fetchJson.rejects(
|
||||||
.stub()
|
new RequestFailedError(
|
||||||
.callsArgWith(1, null, { statusCode: 403 }, null)
|
`/project/${ctx.project_id}/join`,
|
||||||
|
{ method: 'POST' },
|
||||||
|
{ status: 403 }
|
||||||
|
)
|
||||||
|
)
|
||||||
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
|
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -161,9 +150,13 @@ describe('WebApiManager', function () {
|
|||||||
|
|
||||||
describe('when web replies with a 404', function () {
|
describe('when web replies with a 404', function () {
|
||||||
beforeEach(function (ctx) {
|
beforeEach(function (ctx) {
|
||||||
ctx.request.post = sinon
|
ctx.fetchUtils.fetchJson.rejects(
|
||||||
.stub()
|
new RequestFailedError(
|
||||||
.callsArgWith(1, null, { statusCode: 404 }, null)
|
`/project/${ctx.project_id}/join`,
|
||||||
|
{ method: 'POST' },
|
||||||
|
{ status: 404 }
|
||||||
|
)
|
||||||
|
)
|
||||||
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
|
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -181,18 +174,18 @@ describe('WebApiManager', function () {
|
|||||||
|
|
||||||
describe('with an error from web', function () {
|
describe('with an error from web', function () {
|
||||||
beforeEach(function (ctx) {
|
beforeEach(function (ctx) {
|
||||||
ctx.request.post = sinon
|
ctx.fetchUtils.fetchJson.rejects(
|
||||||
.stub()
|
new RequestFailedError(
|
||||||
.callsArgWith(1, null, { statusCode: 500 }, null)
|
`/project/${ctx.project_id}/join`,
|
||||||
return ctx.WebApiManager.joinProject(
|
{ method: 'POST' },
|
||||||
ctx.project_id,
|
{ status: 500 }
|
||||||
ctx.user_id,
|
)
|
||||||
ctx.callback
|
|
||||||
)
|
)
|
||||||
|
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
|
||||||
})
|
})
|
||||||
|
|
||||||
return it('should call the callback with an error', function (ctx) {
|
it('should call the callback with an error', function (ctx) {
|
||||||
return ctx.callback
|
ctx.callback
|
||||||
.calledWith(
|
.calledWith(
|
||||||
sinon.match({
|
sinon.match({
|
||||||
message: 'non-success status code from web',
|
message: 'non-success status code from web',
|
||||||
@@ -205,18 +198,12 @@ describe('WebApiManager', function () {
|
|||||||
|
|
||||||
describe('with no data from web', function () {
|
describe('with no data from web', function () {
|
||||||
beforeEach(function (ctx) {
|
beforeEach(function (ctx) {
|
||||||
ctx.request.post = sinon
|
ctx.fetchUtils.fetchJson.resolves(null)
|
||||||
.stub()
|
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
|
||||||
.callsArgWith(1, null, { statusCode: 200 }, null)
|
|
||||||
return ctx.WebApiManager.joinProject(
|
|
||||||
ctx.project_id,
|
|
||||||
ctx.user_id,
|
|
||||||
ctx.callback
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return it('should call the callback with an error', function (ctx) {
|
it('should call the callback with an error', function (ctx) {
|
||||||
return ctx.callback
|
ctx.callback
|
||||||
.calledWith(
|
.calledWith(
|
||||||
sinon.match({
|
sinon.match({
|
||||||
message: 'no data returned from joinProject request',
|
message: 'no data returned from joinProject request',
|
||||||
@@ -226,20 +213,20 @@ describe('WebApiManager', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return describe('when the project is over its rate limit', function () {
|
describe('when the project is over its rate limit', function () {
|
||||||
beforeEach(function (ctx) {
|
beforeEach(function (ctx) {
|
||||||
ctx.request.post = sinon
|
ctx.fetchUtils.fetchJson.rejects(
|
||||||
.stub()
|
new RequestFailedError(
|
||||||
.callsArgWith(1, null, { statusCode: 429 }, null)
|
`/project/${ctx.project_id}/join`,
|
||||||
return ctx.WebApiManager.joinProject(
|
{ method: 'POST' },
|
||||||
ctx.project_id,
|
{ status: 429 }
|
||||||
ctx.user_id,
|
)
|
||||||
ctx.callback
|
|
||||||
)
|
)
|
||||||
|
ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
|
||||||
})
|
})
|
||||||
|
|
||||||
return it('should call the callback with a TooManyRequests error code', function (ctx) {
|
it('should call the callback with a TooManyRequests error code', function (ctx) {
|
||||||
return ctx.callback
|
ctx.callback
|
||||||
.calledWith(
|
.calledWith(
|
||||||
sinon.match({
|
sinon.match({
|
||||||
message: 'rate-limit hit when joining project',
|
message: 'rate-limit hit when joining project',
|
||||||
|
|||||||
Reference in New Issue
Block a user