mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 06:03:42 -05:00
Create joinDoc socket.io end point
This commit is contained in:
parent
919b192e16
commit
eb8ccc0298
13 changed files with 516 additions and 56 deletions
12
services/real-time/app/coffee/AuthorizationManager.coffee
Normal file
12
services/real-time/app/coffee/AuthorizationManager.coffee
Normal file
|
@ -0,0 +1,12 @@
|
|||
module.exports = AuthorizationManager =
|
||||
assertClientCanViewProject: (client, callback = (error) ->) ->
|
||||
AuthorizationManager._assertClientHasPrivilegeLevel client, ["readOnly", "readAndWrite", "owner"], callback
|
||||
|
||||
_assertClientHasPrivilegeLevel: (client, allowedLevels, callback = (error) ->) ->
|
||||
client.get "privilege_level", (error, privilegeLevel) ->
|
||||
return callback(error) if error?
|
||||
allowed = (privilegeLevel in allowedLevels)
|
||||
if allowed
|
||||
callback null
|
||||
else
|
||||
callback new Error("not authorized")
|
26
services/real-time/app/coffee/DocumentUpdaterManager.coffee
Normal file
26
services/real-time/app/coffee/DocumentUpdaterManager.coffee
Normal file
|
@ -0,0 +1,26 @@
|
|||
request = require "request"
|
||||
logger = require "logger-sharelatex"
|
||||
settings = require "settings-sharelatex"
|
||||
|
||||
module.exports = DocumentUpdaterManager =
|
||||
getDocument: (project_id, doc_id, fromVersion, callback = (error, exists, doclines, version) ->) ->
|
||||
#timer = new metrics.Timer("get-document")
|
||||
url = "#{settings.apis.documentupdater.url}/project/#{project_id}/doc/#{doc_id}?fromVersion=#{fromVersion}"
|
||||
logger.log {project_id, doc_id, fromVersion}, "getting doc from document updater"
|
||||
request.get url, (err, res, body) ->
|
||||
#timer.done()
|
||||
if err?
|
||||
logger.error {err, url, project_id, doc_id}, "error getting doc from doc updater"
|
||||
return callback(err)
|
||||
if 200 <= res.statusCode < 300
|
||||
logger.log {project_id, doc_id}, "got doc from document document updater"
|
||||
try
|
||||
body = JSON.parse(body)
|
||||
catch error
|
||||
return callback(error)
|
||||
callback null, body?.lines, body?.version, body?.ops
|
||||
else
|
||||
err = new Error("doc updater returned a non-success status code: #{res.statusCode}")
|
||||
err.statusCode = res.statusCode
|
||||
logger.error {err, project_id, doc_id, url}, "doc updater returned a non-success status code: #{res.statusCode}"
|
||||
callback err
|
|
@ -3,6 +3,23 @@ logger = require "logger-sharelatex"
|
|||
WebsocketController = require "./WebsocketController"
|
||||
|
||||
module.exports = Router =
|
||||
# We don't want to send raw errors back to the client, in case they
|
||||
# contain sensitive data. Instead we log them out, and send a generic
|
||||
# JSON object which can be serialized over socket.io
|
||||
_createCallbackWithErrorFilter: (client, method, callback) ->
|
||||
return (err, args...) ->
|
||||
if err?
|
||||
|
||||
err = {message: "Something went wrong"}
|
||||
callback err, args...
|
||||
|
||||
# Used in error reporting
|
||||
_getClientData: (client, callback = (error, data) ->) ->
|
||||
client.get "user_id", (error, user_id) ->
|
||||
client.get "project_id", (error, project_id) ->
|
||||
client.get "doc_id", (error, doc_id) ->
|
||||
callback null, { id: client.id, user_id, project_id, doc_id }
|
||||
|
||||
configure: (app, io, session) ->
|
||||
session.on 'connection', (error, client, session) ->
|
||||
if error?
|
||||
|
@ -12,7 +29,7 @@ module.exports = Router =
|
|||
|
||||
Metrics.inc('socket-io.connection')
|
||||
|
||||
logger.log session: session, "got session"
|
||||
logger.log session: session, client_id: client.id, "client connected"
|
||||
|
||||
user = session.user
|
||||
if !user? or !user._id?
|
||||
|
@ -21,5 +38,28 @@ module.exports = Router =
|
|||
return
|
||||
|
||||
client.on "joinProject", (data = {}, callback) ->
|
||||
WebsocketController.joinProject(client, user, data?.project_id, callback)
|
||||
WebsocketController.joinProject client, user, data.project_id, (err, args...) ->
|
||||
if err?
|
||||
Router._getClientData client, (_, client) ->
|
||||
logger.error {err, client, project_id: data.project_id}, "server side error in joinProject"
|
||||
# Don't return raw error to prevent leaking server side info
|
||||
return callback {message: "Something went wrong"}
|
||||
else
|
||||
callback(null, args...)
|
||||
|
||||
|
||||
client.on "joinDoc", (doc_id, fromVersion, callback) ->
|
||||
# fromVersion is optional
|
||||
if typeof fromVersion == "function"
|
||||
callback = fromVersion
|
||||
fromVersion = -1
|
||||
|
||||
WebsocketController.joinDoc client, doc_id, fromVersion, (err, args...) ->
|
||||
if err?
|
||||
Router._getClientData client, (_, client) ->
|
||||
logger.error {err, client, doc_id, fromVersion}, "server side error in joinDoc"
|
||||
# Don't return raw error to prevent leaking server side info
|
||||
return callback {message: "Something went wrong"}
|
||||
else
|
||||
callback(null, args...)
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
logger = require "logger-sharelatex"
|
||||
WebApiManager = require "./WebApiManager"
|
||||
AuthorizationManager = require "./AuthorizationManager"
|
||||
DocumentUpdaterManager = require "./DocumentUpdaterManager"
|
||||
|
||||
module.exports = WebsocketController =
|
||||
# If the protocol version changes when the client reconnects,
|
||||
|
@ -9,17 +11,16 @@ module.exports = WebsocketController =
|
|||
|
||||
joinProject: (client, user, project_id, callback = (error, project, privilegeLevel, protocolVersion) ->) ->
|
||||
user_id = user?._id
|
||||
logger.log {user_id, project_id}, "user joining project"
|
||||
logger.log {user_id, project_id, client_id: client.id}, "user joining project"
|
||||
WebApiManager.joinProject project_id, user_id, (error, project, privilegeLevel) ->
|
||||
return callback(error) if error?
|
||||
|
||||
if !privilegeLevel or privilegeLevel == ""
|
||||
err = new Error("not authorized")
|
||||
logger.error {err, project_id, user_id}, "user is not authorized to join project"
|
||||
# Don't send an error object since socket.io can apparently
|
||||
# only serialize JSON.
|
||||
return callback({message: err.message})
|
||||
logger.error {err, project_id, user_id, client_id: client.id}, "user is not authorized to join project"
|
||||
return callback(err)
|
||||
|
||||
client.set("privilege_level", privilegeLevel)
|
||||
client.set("user_id", user_id)
|
||||
client.set("project_id", project_id)
|
||||
client.set("owner_id", project?.owner?._id)
|
||||
|
@ -31,3 +32,29 @@ module.exports = WebsocketController =
|
|||
client.set("login_count", user?.loginCount)
|
||||
|
||||
callback null, project, privilegeLevel, WebsocketController.PROTOCOL_VERSION
|
||||
|
||||
joinDoc: (client, doc_id, fromVersion = -1, callback = (error, doclines, version, ops) ->) ->
|
||||
client.get "user_id", (error, user_id) ->
|
||||
client.get "project_id", (error, project_id) ->
|
||||
logger.log {user_id, project_id, doc_id, fromVersion, client_id: client.id}, "client joining doc"
|
||||
|
||||
AuthorizationManager.assertClientCanViewProject client, (error) ->
|
||||
return callback(error) if error?
|
||||
client.get "project_id", (error, project_id) ->
|
||||
return callback(error) if error?
|
||||
return callback(new Error("no project_id found on client")) if !project_id?
|
||||
DocumentUpdaterManager.getDocument project_id, doc_id, fromVersion, (error, lines, version, ops) ->
|
||||
return callback(error) if error?
|
||||
# Encode any binary bits of data so it can go via WebSockets
|
||||
# See http://ecmanaut.blogspot.co.uk/2006/07/encoding-decoding-utf8-in-javascript.html
|
||||
escapedLines = []
|
||||
for line in lines
|
||||
try
|
||||
line = unescape(encodeURIComponent(line))
|
||||
catch err
|
||||
logger.err {err, project_id, doc_id, fromVersion, line, client_id: client.id}, "error encoding line uri component"
|
||||
return callback(err)
|
||||
escapedLines.push line
|
||||
client.join(doc_id)
|
||||
callback null, escapedLines, version, ops
|
||||
|
|
@ -13,6 +13,8 @@ module.exports =
|
|||
apis:
|
||||
web:
|
||||
url: "http://localhost:3000"
|
||||
documentupdater:
|
||||
url: "http://localhost:3003"
|
||||
|
||||
security:
|
||||
sessionSecret: "secret-please-change"
|
||||
|
|
118
services/real-time/test/acceptance/coffee/JoinDocTests.coffee
Normal file
118
services/real-time/test/acceptance/coffee/JoinDocTests.coffee
Normal file
|
@ -0,0 +1,118 @@
|
|||
chai = require("chai")
|
||||
expect = chai.expect
|
||||
chai.should()
|
||||
|
||||
RealTimeClient = require "./helpers/RealTimeClient"
|
||||
MockDocUpdaterServer = require "./helpers/MockDocUpdaterServer"
|
||||
FixturesManager = require "./helpers/FixturesManager"
|
||||
|
||||
describe "joinDoc", ->
|
||||
before ->
|
||||
@lines = ["test", "doc", "lines"]
|
||||
@version = 42
|
||||
@ops = ["mock", "doc", "ops"]
|
||||
|
||||
describe "when authorised readAndWrite", ->
|
||||
before (done) ->
|
||||
FixturesManager.setUpProject {
|
||||
privilegeLevel: "readAndWrite"
|
||||
}, (error, data) =>
|
||||
throw error if error?
|
||||
{@project_id, @user_id} = data
|
||||
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (error, data) =>
|
||||
throw error if error?
|
||||
{@doc_id} = data
|
||||
@client = RealTimeClient.connect()
|
||||
@client.emit "joinProject", project_id: @project_id, (error) =>
|
||||
throw error if error?
|
||||
@client.emit "joinDoc", @doc_id, (error, @returnedArgs...) =>
|
||||
throw error if error?
|
||||
done()
|
||||
|
||||
it "should get the doc from the doc updater", ->
|
||||
MockDocUpdaterServer.getDocument
|
||||
.calledWith(@project_id, @doc_id, -1)
|
||||
.should.equal true
|
||||
|
||||
it "should return the doc lines, version and ops", ->
|
||||
@returnedArgs.should.deep.equal [@lines, @version, @ops]
|
||||
|
||||
describe "when authorised readOnly", ->
|
||||
before (done) ->
|
||||
FixturesManager.setUpProject {
|
||||
privilegeLevel: "readOnly"
|
||||
}, (error, data) =>
|
||||
throw error if error?
|
||||
{@project_id, @user_id} = data
|
||||
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (error, data) =>
|
||||
throw error if error?
|
||||
{@doc_id} = data
|
||||
@client = RealTimeClient.connect()
|
||||
@client.emit "joinProject", project_id: @project_id, (error) =>
|
||||
throw error if error?
|
||||
@client.emit "joinDoc", @doc_id, (error, @returnedArgs...) =>
|
||||
throw error if error?
|
||||
done()
|
||||
|
||||
it "should get the doc from the doc updater", ->
|
||||
MockDocUpdaterServer.getDocument
|
||||
.calledWith(@project_id, @doc_id, -1)
|
||||
.should.equal true
|
||||
|
||||
it "should return the doc lines, version and ops", ->
|
||||
@returnedArgs.should.deep.equal [@lines, @version, @ops]
|
||||
|
||||
describe "when authorised as owner", ->
|
||||
before (done) ->
|
||||
FixturesManager.setUpProject {
|
||||
privilegeLevel: "owner"
|
||||
}, (error, data) =>
|
||||
throw error if error?
|
||||
{@project_id, @user_id} = data
|
||||
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (error, data) =>
|
||||
throw error if error?
|
||||
{@doc_id} = data
|
||||
@client = RealTimeClient.connect()
|
||||
@client.emit "joinProject", project_id: @project_id, (error) =>
|
||||
throw error if error?
|
||||
@client.emit "joinDoc", @doc_id, (error, @returnedArgs...) =>
|
||||
throw error if error?
|
||||
done()
|
||||
|
||||
it "should get the doc from the doc updater", ->
|
||||
MockDocUpdaterServer.getDocument
|
||||
.calledWith(@project_id, @doc_id, -1)
|
||||
.should.equal true
|
||||
|
||||
it "should return the doc lines, version and ops", ->
|
||||
@returnedArgs.should.deep.equal [@lines, @version, @ops]
|
||||
|
||||
# It is impossible to write an acceptance test to test joining an unauthorized
|
||||
# project, since joinProject already catches that. If you can join a project,
|
||||
# then you can join a doc in that project.
|
||||
|
||||
describe "with a fromVersion", ->
|
||||
before (done) ->
|
||||
@fromVersion = 36
|
||||
FixturesManager.setUpProject {
|
||||
privilegeLevel: "readAndWrite"
|
||||
}, (error, data) =>
|
||||
throw error if error?
|
||||
{@project_id, @user_id} = data
|
||||
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (error, data) =>
|
||||
throw error if error?
|
||||
{@doc_id} = data
|
||||
@client = RealTimeClient.connect()
|
||||
@client.emit "joinProject", project_id: @project_id, (error) =>
|
||||
throw error if error?
|
||||
@client.emit "joinDoc", @doc_id, @fromVersion, (error, @returnedArgs...) =>
|
||||
throw error if error?
|
||||
done()
|
||||
|
||||
it "should get the doc from the doc updater with the fromVersion", ->
|
||||
MockDocUpdaterServer.getDocument
|
||||
.calledWith(@project_id, @doc_id, @fromVersion)
|
||||
.should.equal true
|
||||
|
||||
it "should return the doc lines, version and ops", ->
|
||||
@returnedArgs.should.deep.equal [@lines, @version, @ops]
|
|
@ -3,24 +3,21 @@ expect = chai.expect
|
|||
chai.should()
|
||||
|
||||
RealTimeClient = require "./helpers/RealTimeClient"
|
||||
MockWebClient = require "./helpers/MockWebClient"
|
||||
MockWebServer = require "./helpers/MockWebServer"
|
||||
FixturesManager = require "./helpers/FixturesManager"
|
||||
|
||||
|
||||
describe "joinProject", ->
|
||||
describe "when authorized", ->
|
||||
before (done) ->
|
||||
@user_id = "mock-user-id"
|
||||
@project_id = "mock-project-id"
|
||||
privileges = {}
|
||||
privileges[@user_id] = "owner"
|
||||
MockWebClient.createMockProject(@project_id, privileges, {
|
||||
FixturesManager.setUpProject {
|
||||
privilegeLevel: "owner"
|
||||
project: {
|
||||
name: "Test Project"
|
||||
})
|
||||
MockWebClient.run (error) =>
|
||||
throw error if error?
|
||||
RealTimeClient.setSession {
|
||||
user: { _id: @user_id }
|
||||
}, (error) =>
|
||||
}
|
||||
}, (error, data) =>
|
||||
throw error if error?
|
||||
{@user_id, @project_id} = data
|
||||
@client = RealTimeClient.connect()
|
||||
@client.emit "joinProject", {
|
||||
project_id: @project_id
|
||||
|
@ -29,7 +26,7 @@ describe "joinProject", ->
|
|||
done()
|
||||
|
||||
it "should get the project from web", ->
|
||||
MockWebClient.joinProject
|
||||
MockWebServer.joinProject
|
||||
.calledWith(@project_id, @user_id)
|
||||
.should.equal true
|
||||
|
||||
|
@ -46,18 +43,14 @@ describe "joinProject", ->
|
|||
|
||||
describe "when not authorized", ->
|
||||
before (done) ->
|
||||
@user_id = "mock-user-id-2"
|
||||
@project_id = "mock-project-id-2"
|
||||
privileges = {}
|
||||
MockWebClient.createMockProject(@project_id, privileges, {
|
||||
FixturesManager.setUpProject {
|
||||
privilegeLevel: null
|
||||
project: {
|
||||
name: "Test Project"
|
||||
})
|
||||
MockWebClient.run (error) =>
|
||||
throw error if error?
|
||||
RealTimeClient.setSession {
|
||||
user: { _id: @user_id }
|
||||
}, (error) =>
|
||||
}
|
||||
}, (error, data) =>
|
||||
throw error if error?
|
||||
{@user_id, @project_id} = data
|
||||
@client = RealTimeClient.connect()
|
||||
@client.emit "joinProject", {
|
||||
project_id: @project_id
|
||||
|
@ -65,4 +58,5 @@ describe "joinProject", ->
|
|||
done()
|
||||
|
||||
it "should return an error", ->
|
||||
@error.message.should.equal "not authorized"
|
||||
# We don't return specific errors
|
||||
@error.message.should.equal "Something went wrong"
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
RealTimeClient = require "./RealTimeClient"
|
||||
MockWebServer = require "./MockWebServer"
|
||||
MockDocUpdaterServer = require "./MockDocUpdaterServer"
|
||||
|
||||
module.exports = FixturesManager =
|
||||
setUpProject: (options = {}, callback = (error, data) ->) ->
|
||||
options.user_id ||= FixturesManager.getRandomId()
|
||||
options.project_id ||= FixturesManager.getRandomId()
|
||||
options.project ||= { name: "Test Project" }
|
||||
{project_id, user_id, privilegeLevel, project} = options
|
||||
|
||||
privileges = {}
|
||||
privileges[user_id] = privilegeLevel
|
||||
|
||||
MockWebServer.createMockProject(project_id, privileges, project)
|
||||
MockWebServer.run (error) =>
|
||||
throw error if error?
|
||||
RealTimeClient.setSession {
|
||||
user: { _id: user_id }
|
||||
}, (error) =>
|
||||
throw error if error?
|
||||
callback null, {project_id, user_id, privilegeLevel, project}
|
||||
|
||||
setUpDoc: (project_id, options = {}, callback = (error, data) ->) ->
|
||||
options.doc_id ||= FixturesManager.getRandomId()
|
||||
options.lines ||= ["doc", "lines"]
|
||||
options.version ||= 42
|
||||
options.ops ||= ["mock", "ops"]
|
||||
{doc_id, lines, version, ops} = options
|
||||
|
||||
MockDocUpdaterServer.createMockDoc project_id, doc_id, {lines, version, ops}
|
||||
MockDocUpdaterServer.run (error) =>
|
||||
throw error if error?
|
||||
callback null, {project_id, doc_id, lines, version, ops}
|
||||
|
||||
getRandomId: () ->
|
||||
return require("crypto")
|
||||
.createHash("sha1")
|
||||
.update(Math.random().toString())
|
||||
.digest("hex")
|
||||
.slice(0,24)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
sinon = require "sinon"
|
||||
express = require "express"
|
||||
|
||||
module.exports = MockDocUpdaterServer =
|
||||
docs: {}
|
||||
|
||||
createMockDoc: (project_id, doc_id, data) ->
|
||||
MockDocUpdaterServer.docs["#{project_id}:#{doc_id}"] = data
|
||||
|
||||
getDocument: (project_id, doc_id, fromVersion, callback = (error, data) ->) ->
|
||||
callback(
|
||||
null, MockDocUpdaterServer.docs["#{project_id}:#{doc_id}"]
|
||||
)
|
||||
|
||||
getDocumentRequest: (req, res, next) ->
|
||||
{project_id, doc_id} = req.params
|
||||
{fromVersion} = req.query
|
||||
fromVersion = parseInt(fromVersion, 10)
|
||||
MockDocUpdaterServer.getDocument project_id, doc_id, fromVersion, (error, data) ->
|
||||
return next(error) if error?
|
||||
res.json data
|
||||
|
||||
running: false
|
||||
run: (callback = (error) ->) ->
|
||||
if MockDocUpdaterServer.running
|
||||
return callback()
|
||||
app = express()
|
||||
app.get "/project/:project_id/doc/:doc_id", MockDocUpdaterServer.getDocumentRequest
|
||||
app.listen 3003, (error) ->
|
||||
MockDocUpdaterServer.running = true
|
||||
callback(error)
|
||||
|
||||
sinon.spy MockDocUpdaterServer, "getDocument"
|
|
@ -1,25 +1,25 @@
|
|||
sinon = require "sinon"
|
||||
express = require "express"
|
||||
|
||||
module.exports = MockWebClient =
|
||||
module.exports = MockWebServer =
|
||||
projects: {}
|
||||
privileges: {}
|
||||
|
||||
createMockProject: (project_id, privileges, project) ->
|
||||
MockWebClient.privileges[project_id] = privileges
|
||||
MockWebClient.projects[project_id] = project
|
||||
MockWebServer.privileges[project_id] = privileges
|
||||
MockWebServer.projects[project_id] = project
|
||||
|
||||
joinProject: (project_id, user_id, callback = (error, project, privilegeLevel) ->) ->
|
||||
callback(
|
||||
null,
|
||||
MockWebClient.projects[project_id],
|
||||
MockWebClient.privileges[project_id][user_id]
|
||||
MockWebServer.projects[project_id],
|
||||
MockWebServer.privileges[project_id][user_id]
|
||||
)
|
||||
|
||||
joinProjectRequest: (req, res, next) ->
|
||||
{project_id} = req.params
|
||||
{user_id} = req.query
|
||||
MockWebClient.joinProject project_id, user_id, (error, project, privilegeLevel) ->
|
||||
MockWebServer.joinProject project_id, user_id, (error, project, privilegeLevel) ->
|
||||
return next(error) if error?
|
||||
res.json {
|
||||
project: project
|
||||
|
@ -28,12 +28,12 @@ module.exports = MockWebClient =
|
|||
|
||||
running: false
|
||||
run: (callback = (error) ->) ->
|
||||
if MockWebClient.running
|
||||
if MockWebServer.running
|
||||
return callback()
|
||||
app = express()
|
||||
app.post "/project/:project_id/join", MockWebClient.joinProjectRequest
|
||||
app.post "/project/:project_id/join", MockWebServer.joinProjectRequest
|
||||
app.listen 3000, (error) ->
|
||||
MockWebClient.running = true
|
||||
MockWebServer.running = true
|
||||
callback(error)
|
||||
|
||||
sinon.spy MockWebClient, "joinProject"
|
||||
sinon.spy MockWebServer, "joinProject"
|
|
@ -0,0 +1,39 @@
|
|||
chai = require "chai"
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
sinon = require("sinon")
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
path = require "path"
|
||||
modulePath = '../../../app/js/AuthorizationManager'
|
||||
|
||||
describe 'AuthorizationManager', ->
|
||||
beforeEach ->
|
||||
@client =
|
||||
params: {}
|
||||
get: (param, cb) -> cb null, @params[param]
|
||||
@AuthorizationManager = SandboxedModule.require modulePath, requires: {}
|
||||
|
||||
describe "assertClientCanViewProject", ->
|
||||
it "should allow the readOnly privilegeLevel", (done) ->
|
||||
@client.params.privilege_level = "readOnly"
|
||||
@AuthorizationManager.assertClientCanViewProject @client, (error) ->
|
||||
expect(error).to.be.null
|
||||
done()
|
||||
|
||||
it "should allow the readAndWrite privilegeLevel", (done) ->
|
||||
@client.params.privilege_level = "readAndWrite"
|
||||
@AuthorizationManager.assertClientCanViewProject @client, (error) ->
|
||||
expect(error).to.be.null
|
||||
done()
|
||||
|
||||
it "should allow the owner privilegeLevel", (done) ->
|
||||
@client.params.privilege_level = "owner"
|
||||
@AuthorizationManager.assertClientCanViewProject @client, (error) ->
|
||||
expect(error).to.be.null
|
||||
done()
|
||||
|
||||
it "should return an error with any other privilegeLevel", (done) ->
|
||||
@client.params.privilege_level = "unknown"
|
||||
@AuthorizationManager.assertClientCanViewProject @client, (error) ->
|
||||
error.message.should.equal "not authorized"
|
||||
done()
|
|
@ -0,0 +1,60 @@
|
|||
require('chai').should()
|
||||
sinon = require("sinon")
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
path = require "path"
|
||||
modulePath = '../../../app/js/DocumentUpdaterManager'
|
||||
|
||||
describe 'DocumentUpdaterManager', ->
|
||||
beforeEach ->
|
||||
@project_id = "project-id-923"
|
||||
@doc_id = "doc-id-394"
|
||||
@lines = ["one", "two", "three"]
|
||||
@version = 42
|
||||
@settings =
|
||||
apis: documentupdater: url: "http://doc-updater.example.com"
|
||||
|
||||
@DocumentUpdaterManager = SandboxedModule.require modulePath, requires:
|
||||
'settings-sharelatex':@settings
|
||||
'logger-sharelatex': @logger = {log: sinon.stub(), error: sinon.stub()}
|
||||
'request': @request = {}
|
||||
|
||||
describe "getDocument", ->
|
||||
beforeEach ->
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "successfully", ->
|
||||
beforeEach ->
|
||||
@body = JSON.stringify
|
||||
lines: @lines
|
||||
version: @version
|
||||
ops: @ops = ["mock-op-1", "mock-op-2"]
|
||||
@fromVersion = 2
|
||||
@request.get = sinon.stub().callsArgWith(1, null, {statusCode: 200}, @body)
|
||||
@DocumentUpdaterManager.getDocument @project_id, @doc_id, @fromVersion, @callback
|
||||
|
||||
it 'should get the document from the document updater', ->
|
||||
url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}/doc/#{@doc_id}?fromVersion=#{@fromVersion}"
|
||||
@request.get.calledWith(url).should.equal true
|
||||
|
||||
it "should call the callback with the lines and version", ->
|
||||
@callback.calledWith(null, @lines, @version, @ops).should.equal true
|
||||
|
||||
describe "when the document updater API returns an error", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, @error = new Error("something went wrong"), null, null)
|
||||
@DocumentUpdaterManager.getDocument @project_id, @doc_id, @fromVersion, @callback
|
||||
|
||||
it "should return an error to the callback", ->
|
||||
@callback.calledWith(@error).should.equal true
|
||||
|
||||
describe "when the document updater returns a failure error code", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, null, { statusCode: 500 }, "")
|
||||
@DocumentUpdaterManager.getDocument @project_id, @doc_id, @fromVersion, @callback
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
err = new Error("doc updater returned failure status code: 500")
|
||||
err.statusCode = 500
|
||||
@callback
|
||||
.calledWith(err)
|
||||
.should.equal true
|
|
@ -19,10 +19,14 @@ describe 'WebsocketController', ->
|
|||
}
|
||||
@callback = sinon.stub()
|
||||
@client =
|
||||
params: {}
|
||||
set: sinon.stub()
|
||||
get: (param, cb) -> cb null, @params[param]
|
||||
join: sinon.stub()
|
||||
@WebsocketController = SandboxedModule.require modulePath, requires:
|
||||
"./WebApiManager": @WebApiManager = {}
|
||||
"./AuthorizationManager": @AuthorizationManager = {}
|
||||
"./DocumentUpdaterManager": @DocumentUpdaterManager = {}
|
||||
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
|
||||
|
||||
afterEach ->
|
||||
|
@ -46,6 +50,9 @@ describe 'WebsocketController', ->
|
|||
.calledWith(@project_id, @user._id)
|
||||
.should.equal true
|
||||
|
||||
it "should set the privilege level on the client", ->
|
||||
@client.set.calledWith("privilege_level", @privilegeLevel).should.equal true
|
||||
|
||||
it "should set the user's id on the client", ->
|
||||
@client.set.calledWith("user_id", @user._id).should.equal true
|
||||
|
||||
|
@ -85,5 +92,65 @@ describe 'WebsocketController', ->
|
|||
|
||||
it "should return an error", ->
|
||||
@callback
|
||||
.calledWith({message: "not authorized"})
|
||||
.calledWith(new Error("not authorized"))
|
||||
.should.equal true
|
||||
|
||||
describe "joinDoc", ->
|
||||
beforeEach ->
|
||||
@doc_id = "doc-id-123"
|
||||
@doc_lines = ["doc", "lines"]
|
||||
@version = 42
|
||||
@ops = ["mock", "ops"]
|
||||
|
||||
@client.params.project_id = @project_id
|
||||
|
||||
@AuthorizationManager.assertClientCanViewProject = sinon.stub().callsArgWith(1, null)
|
||||
@DocumentUpdaterManager.getDocument = sinon.stub().callsArgWith(3, null, @doc_lines, @version, @ops)
|
||||
|
||||
describe "with a fromVersion", ->
|
||||
beforeEach ->
|
||||
@fromVersion = 40
|
||||
@WebsocketController.joinDoc @client, @doc_id, @fromVersion, @callback
|
||||
|
||||
it "should check that the client is authorized to view the project", ->
|
||||
@AuthorizationManager.assertClientCanViewProject
|
||||
.calledWith(@client)
|
||||
.should.equal true
|
||||
|
||||
it "should get the document from the DocumentUpdaterManager", ->
|
||||
@DocumentUpdaterManager.getDocument
|
||||
.calledWith(@project_id, @doc_id, @fromVersion)
|
||||
.should.equal true
|
||||
|
||||
it "should join the client to room for the doc_id", ->
|
||||
@client.join
|
||||
.calledWith(@doc_id)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback with the lines, version and ops", ->
|
||||
@callback
|
||||
.calledWith(null, @doc_lines, @version, @ops)
|
||||
.should.equal true
|
||||
|
||||
describe "with doclines that need escaping", ->
|
||||
beforeEach ->
|
||||
@doc_lines.push ["räksmörgås"]
|
||||
@WebsocketController.joinDoc @client, @doc_id, -1, @callback
|
||||
|
||||
it "should call the callback with the escaped lines", ->
|
||||
escaped_lines = @callback.args[0][1]
|
||||
escaped_word = escaped_lines.pop()
|
||||
escaped_word.should.equal 'räksmörgås'
|
||||
# Check that unescaping works
|
||||
decodeURIComponent(escape(escaped_word)).should.equal "räksmörgås"
|
||||
|
||||
describe "when not authorized", ->
|
||||
beforeEach ->
|
||||
@AuthorizationManager.assertClientCanViewProject = sinon.stub().callsArgWith(1, @err = new Error("not authorized"))
|
||||
@WebsocketController.joinDoc @client, @doc_id, -1, @callback
|
||||
|
||||
it "should call the callback with an error", ->
|
||||
@callback.calledWith(@err).should.equal true
|
||||
|
||||
it "should not call the DocumentUpdaterManager", ->
|
||||
@DocumentUpdaterManager.getDocument.called.should.equal false
|
Loading…
Reference in a new issue