first commit
This commit is contained in:
156
services/real-time/test/acceptance/js/helpers/FixturesManager.js
Normal file
156
services/real-time/test/acceptance/js/helpers/FixturesManager.js
Normal file
@@ -0,0 +1,156 @@
|
||||
// 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
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let FixturesManager
|
||||
const RealTimeClient = require('./RealTimeClient')
|
||||
const MockWebServer = require('./MockWebServer')
|
||||
const MockDocUpdaterServer = require('./MockDocUpdaterServer')
|
||||
|
||||
module.exports = FixturesManager = {
|
||||
setUpProject(options, callback) {
|
||||
if (options == null) {
|
||||
options = {}
|
||||
}
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
if (!options.user_id) {
|
||||
options.user_id = FixturesManager.getRandomId()
|
||||
}
|
||||
if (!options.project_id) {
|
||||
options.project_id = FixturesManager.getRandomId()
|
||||
}
|
||||
if (!options.project) {
|
||||
options.project = { name: 'Test Project' }
|
||||
}
|
||||
let {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
privilegeLevel,
|
||||
project,
|
||||
publicAccess,
|
||||
userMetadata,
|
||||
anonymousAccessToken,
|
||||
} = options
|
||||
|
||||
if (privilegeLevel === 'owner') {
|
||||
project.owner = { _id: userId }
|
||||
} else {
|
||||
project.owner = { _id: '404404404404404404404404' }
|
||||
}
|
||||
|
||||
const privileges = {}
|
||||
privileges[userId] = privilegeLevel
|
||||
if (publicAccess) {
|
||||
anonymousAccessToken =
|
||||
anonymousAccessToken || FixturesManager.getRandomId()
|
||||
privileges[anonymousAccessToken] = publicAccess
|
||||
}
|
||||
|
||||
const metadataByUser = {}
|
||||
metadataByUser[userId] = userMetadata
|
||||
|
||||
MockWebServer.createMockProject(
|
||||
projectId,
|
||||
privileges,
|
||||
project,
|
||||
metadataByUser
|
||||
)
|
||||
return MockWebServer.run(error => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
return RealTimeClient.setSession(
|
||||
{
|
||||
user: {
|
||||
_id: userId,
|
||||
first_name: 'Joe',
|
||||
last_name: 'Bloggs',
|
||||
},
|
||||
},
|
||||
error => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
return callback(null, {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
privilegeLevel,
|
||||
project,
|
||||
anonymousAccessToken,
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
setUpDoc(projectId, options, callback) {
|
||||
if (options == null) {
|
||||
options = {}
|
||||
}
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
if (!options.doc_id) {
|
||||
options.doc_id = FixturesManager.getRandomId()
|
||||
}
|
||||
if (!options.lines) {
|
||||
options.lines = ['doc', 'lines']
|
||||
}
|
||||
if (!options.version) {
|
||||
options.version = 42
|
||||
}
|
||||
if (!options.ops) {
|
||||
options.ops = ['mock', 'ops']
|
||||
}
|
||||
const { doc_id: docId, lines, version, ops, ranges } = options
|
||||
|
||||
MockDocUpdaterServer.createMockDoc(projectId, docId, {
|
||||
lines,
|
||||
version,
|
||||
ops,
|
||||
ranges,
|
||||
})
|
||||
return MockDocUpdaterServer.run(error => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
return callback(null, {
|
||||
project_id: projectId,
|
||||
doc_id: docId,
|
||||
lines,
|
||||
version,
|
||||
ops,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
setUpEditorSession(options, callback) {
|
||||
FixturesManager.setUpProject(options, (err, detailsProject) => {
|
||||
if (err) return callback(err)
|
||||
|
||||
FixturesManager.setUpDoc(
|
||||
detailsProject.project_id,
|
||||
options,
|
||||
(err, detailsDoc) => {
|
||||
if (err) return callback(err)
|
||||
|
||||
callback(null, Object.assign({}, detailsProject, detailsDoc))
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
getRandomId() {
|
||||
return require('node:crypto')
|
||||
.createHash('sha1')
|
||||
.update(Math.random().toString())
|
||||
.digest('hex')
|
||||
.slice(0, 24)
|
||||
},
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
/* eslint-disable
|
||||
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
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let MockDocUpdaterServer
|
||||
const sinon = require('sinon')
|
||||
const express = require('express')
|
||||
|
||||
module.exports = MockDocUpdaterServer = {
|
||||
docs: {},
|
||||
|
||||
createMockDoc(projectId, docId, data) {
|
||||
return (MockDocUpdaterServer.docs[`${projectId}:${docId}`] = data)
|
||||
},
|
||||
|
||||
getDocument(projectId, docId, fromVersion, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return callback(null, MockDocUpdaterServer.docs[`${projectId}:${docId}`])
|
||||
},
|
||||
|
||||
deleteProject: sinon.stub().callsArg(1),
|
||||
|
||||
getDocumentRequest(req, res, next) {
|
||||
const { project_id: projectId, doc_id: docId } = req.params
|
||||
let { fromVersion } = req.query
|
||||
fromVersion = parseInt(fromVersion, 10)
|
||||
return MockDocUpdaterServer.getDocument(
|
||||
projectId,
|
||||
docId,
|
||||
fromVersion,
|
||||
(error, data) => {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
if (!data) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
return res.json(data)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
deleteProjectRequest(req, res, next) {
|
||||
const { project_id: projectId } = req.params
|
||||
return MockDocUpdaterServer.deleteProject(projectId, error => {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
return res.sendStatus(204)
|
||||
})
|
||||
},
|
||||
|
||||
running: false,
|
||||
run(callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
if (MockDocUpdaterServer.running) {
|
||||
return callback()
|
||||
}
|
||||
const app = express()
|
||||
app.get(
|
||||
'/project/:project_id/doc/:doc_id',
|
||||
MockDocUpdaterServer.getDocumentRequest
|
||||
)
|
||||
app.delete(
|
||||
'/project/:project_id',
|
||||
MockDocUpdaterServer.deleteProjectRequest
|
||||
)
|
||||
return app
|
||||
.listen(3003, error => {
|
||||
MockDocUpdaterServer.running = true
|
||||
return callback(error)
|
||||
})
|
||||
.on('error', error => {
|
||||
console.error('error starting MockDocUpdaterServer:', error.message)
|
||||
return process.exit(1)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
sinon.spy(MockDocUpdaterServer, 'getDocument')
|
106
services/real-time/test/acceptance/js/helpers/MockWebServer.js
Normal file
106
services/real-time/test/acceptance/js/helpers/MockWebServer.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/* eslint-disable
|
||||
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
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let MockWebServer
|
||||
const sinon = require('sinon')
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
|
||||
module.exports = MockWebServer = {
|
||||
projects: {},
|
||||
privileges: {},
|
||||
userMetadata: {},
|
||||
|
||||
createMockProject(projectId, privileges, project, metadataByUser) {
|
||||
MockWebServer.privileges[projectId] = privileges
|
||||
MockWebServer.userMetadata[projectId] = metadataByUser
|
||||
return (MockWebServer.projects[projectId] = project)
|
||||
},
|
||||
|
||||
inviteUserToProject(projectId, user, privileges) {
|
||||
MockWebServer.privileges[projectId][user._id] = privileges
|
||||
MockWebServer.userMetadata[projectId][user._id] = user
|
||||
},
|
||||
|
||||
joinProject(projectId, userId, anonymousAccessToken, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
const project = MockWebServer.projects[projectId]
|
||||
const privilegeLevel =
|
||||
MockWebServer.privileges[projectId]?.[userId] ||
|
||||
MockWebServer.privileges[projectId]?.[anonymousAccessToken]
|
||||
const userMetadata = MockWebServer.userMetadata[projectId]?.[userId]
|
||||
return callback(null, project, privilegeLevel, userMetadata)
|
||||
},
|
||||
|
||||
joinProjectRequest(req, res, next) {
|
||||
const { project_id: projectId } = req.params
|
||||
const { anonymousAccessToken, userId } = req.body
|
||||
if (projectId === '404404404404404404404404') {
|
||||
// not-found
|
||||
return res.status(404).send()
|
||||
}
|
||||
if (projectId === '403403403403403403403403') {
|
||||
// forbidden
|
||||
return res.status(403).send()
|
||||
}
|
||||
if (projectId === '429429429429429429429429') {
|
||||
// rate-limited
|
||||
return res.status(429).send()
|
||||
} else {
|
||||
return MockWebServer.joinProject(
|
||||
projectId,
|
||||
userId,
|
||||
anonymousAccessToken,
|
||||
(error, project, privilegeLevel, userMetadata) => {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
if (!project) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
return res.json({
|
||||
project,
|
||||
privilegeLevel,
|
||||
isRestrictedUser: !!userMetadata?.isRestrictedUser,
|
||||
isTokenMember: !!userMetadata?.isTokenMember,
|
||||
isInvitedMember: !!userMetadata?.isInvitedMember,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
running: false,
|
||||
run(callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
if (MockWebServer.running) {
|
||||
return callback()
|
||||
}
|
||||
const app = express()
|
||||
app.use(bodyParser.json())
|
||||
app.post('/project/:project_id/join', MockWebServer.joinProjectRequest)
|
||||
return app
|
||||
.listen(3000, error => {
|
||||
MockWebServer.running = true
|
||||
return callback(error)
|
||||
})
|
||||
.on('error', error => {
|
||||
console.error('error starting MockWebServer:', error.message)
|
||||
return process.exit(1)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
sinon.spy(MockWebServer, 'joinProject')
|
165
services/real-time/test/acceptance/js/helpers/RealTimeClient.js
Normal file
165
services/real-time/test/acceptance/js/helpers/RealTimeClient.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/* eslint-disable
|
||||
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
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let Client
|
||||
const { XMLHttpRequest } = require('../../libs/XMLHttpRequest')
|
||||
const io = require('socket.io-client')
|
||||
const async = require('async')
|
||||
|
||||
const request = require('request')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const redis = require('@overleaf/redis-wrapper')
|
||||
const rclient = redis.createClient(Settings.redis.websessions)
|
||||
|
||||
const uid = require('uid-safe').sync
|
||||
const signature = require('cookie-signature')
|
||||
|
||||
io.util.request = function () {
|
||||
const xhr = new XMLHttpRequest()
|
||||
const _open = xhr.open
|
||||
xhr.open = function () {
|
||||
_open.apply(xhr, arguments)
|
||||
if (Client.cookie != null) {
|
||||
return xhr.setRequestHeader('Cookie', Client.cookie)
|
||||
}
|
||||
}
|
||||
return xhr
|
||||
}
|
||||
|
||||
module.exports = Client = {
|
||||
cookie: null,
|
||||
|
||||
setSession(session, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
const sessionId = uid(24)
|
||||
session.cookie = {}
|
||||
return rclient.set('sess:' + sessionId, JSON.stringify(session), error => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
Client.cookieSignedWith = {}
|
||||
// prepare cookie strings for all supported session secrets
|
||||
for (const secretName of [
|
||||
'sessionSecret',
|
||||
'sessionSecretFallback',
|
||||
'sessionSecretUpcoming',
|
||||
]) {
|
||||
const secret = Settings.security[secretName]
|
||||
const cookieKey = 's:' + signature.sign(sessionId, secret)
|
||||
Client.cookieSignedWith[secretName] =
|
||||
`${Settings.cookieName}=${cookieKey}`
|
||||
}
|
||||
// default to the current session secret
|
||||
Client.cookie = Client.cookieSignedWith.sessionSecret
|
||||
return callback()
|
||||
})
|
||||
},
|
||||
|
||||
setAnonSession(projectId, anonymousAccessToken, callback) {
|
||||
Client.setSession(
|
||||
{
|
||||
anonTokenAccess: {
|
||||
[projectId]: anonymousAccessToken,
|
||||
},
|
||||
},
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
unsetSession(callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
Client.cookie = null
|
||||
return callback()
|
||||
},
|
||||
|
||||
connect(projectId, callback) {
|
||||
const client = io.connect('http://127.0.0.1:3026', {
|
||||
'force new connection': true,
|
||||
query: new URLSearchParams({ projectId }).toString(),
|
||||
})
|
||||
let disconnected = false
|
||||
client.on('disconnect', () => {
|
||||
disconnected = true
|
||||
})
|
||||
client.on('connectionRejected', err => {
|
||||
// Wait for disconnect ahead of continuing with the test sequence.
|
||||
setTimeout(() => {
|
||||
if (!disconnected) {
|
||||
throw new Error('should disconnect after connectionRejected')
|
||||
}
|
||||
callback(err)
|
||||
}, 10)
|
||||
})
|
||||
client.on('joinProjectResponse', resp => {
|
||||
const { publicId, project, permissionsLevel, protocolVersion } = resp
|
||||
client.publicId = publicId
|
||||
callback(null, project, permissionsLevel, protocolVersion)
|
||||
})
|
||||
return client
|
||||
},
|
||||
|
||||
getConnectedClients(callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.get(
|
||||
{
|
||||
url: 'http://127.0.0.1:3026/clients',
|
||||
json: true,
|
||||
},
|
||||
(error, response, data) => callback(error, data)
|
||||
)
|
||||
},
|
||||
|
||||
getConnectedClient(clientId, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.get(
|
||||
{
|
||||
url: `http://127.0.0.1:3026/clients/${clientId}`,
|
||||
json: true,
|
||||
},
|
||||
(error, response, data) => {
|
||||
if (response?.statusCode === 404) {
|
||||
callback(new Error('not found'))
|
||||
} else {
|
||||
callback(error, data)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
disconnectClient(clientId, callback) {
|
||||
request.post(
|
||||
{
|
||||
url: `http://127.0.0.1:3026/client/${clientId}/disconnect`,
|
||||
},
|
||||
(error, response, data) => callback(error, data)
|
||||
)
|
||||
return null
|
||||
},
|
||||
|
||||
disconnectAllClients(callback) {
|
||||
return Client.getConnectedClients((error, clients) => {
|
||||
if (error) return callback(error)
|
||||
async.each(
|
||||
clients,
|
||||
(clientView, cb) => Client.disconnectClient(clientView.client_id, cb),
|
||||
callback
|
||||
)
|
||||
})
|
||||
},
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS103: Rewrite code to no longer use __guard__
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const app = require('../../../../app')
|
||||
const logger = require('@overleaf/logger')
|
||||
const Settings = require('@overleaf/settings')
|
||||
|
||||
module.exports = {
|
||||
running: false,
|
||||
initing: false,
|
||||
callbacks: [],
|
||||
ensureRunning(callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
if (this.running) {
|
||||
return callback()
|
||||
} else if (this.initing) {
|
||||
return this.callbacks.push(callback)
|
||||
} else {
|
||||
this.initing = true
|
||||
this.callbacks.push(callback)
|
||||
return app.listen(
|
||||
__guard__(
|
||||
Settings.internal != null ? Settings.internal.realtime : undefined,
|
||||
x => x.port
|
||||
),
|
||||
'127.0.0.1',
|
||||
error => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
this.running = true
|
||||
logger.info('clsi running in dev mode')
|
||||
|
||||
return (() => {
|
||||
const result = []
|
||||
for (callback of Array.from(this.callbacks)) {
|
||||
result.push(callback())
|
||||
}
|
||||
return result
|
||||
})()
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function __guard__(value, transform) {
|
||||
return typeof value !== 'undefined' && value !== null
|
||||
? transform(value)
|
||||
: undefined
|
||||
}
|
Reference in New Issue
Block a user