Merge branch 'master' into sk-unlisted-projects
This commit is contained in:
@@ -431,3 +431,103 @@ describe "CollaboratorsHandler", ->
|
||||
expect(err).to.not.exist
|
||||
expect(isTokenMember).to.equal false
|
||||
done()
|
||||
|
||||
describe 'transferProjects', ->
|
||||
beforeEach ->
|
||||
@from_user_id = "from-user-id"
|
||||
@to_user_id = "to-user-id"
|
||||
@projects = [{
|
||||
_id: "project-id-1"
|
||||
}, {
|
||||
_id: "project-id-2"
|
||||
}]
|
||||
@Project.find = sinon.stub().yields(null, @projects)
|
||||
@Project.update = sinon.stub().yields()
|
||||
@ProjectEntityHandler.flushProjectToThirdPartyDataStore = sinon.stub().yields()
|
||||
|
||||
describe "successfully", ->
|
||||
beforeEach ->
|
||||
@CollaboratorHandler.transferProjects @from_user_id, @to_user_id, @callback
|
||||
|
||||
it "should look up the affected projects", ->
|
||||
@Project.find
|
||||
.calledWith({
|
||||
$or : [
|
||||
{ owner_ref: @from_user_id }
|
||||
{ collaberator_refs: @from_user_id }
|
||||
{ readOnly_refs: @from_user_id }
|
||||
]
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should transfer owned projects", ->
|
||||
@Project.update
|
||||
.calledWith({
|
||||
owner_ref: @from_user_id
|
||||
}, {
|
||||
$set: { owner_ref: @to_user_id }
|
||||
}, {
|
||||
multi: true
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should transfer collaborator projects", ->
|
||||
@Project.update
|
||||
.calledWith({
|
||||
collaberator_refs: @from_user_id
|
||||
}, {
|
||||
$addToSet: { collaberator_refs: @to_user_id }
|
||||
}, {
|
||||
multi: true
|
||||
})
|
||||
.should.equal true
|
||||
@Project.update
|
||||
.calledWith({
|
||||
collaberator_refs: @from_user_id
|
||||
}, {
|
||||
$pull: { collaberator_refs: @from_user_id }
|
||||
}, {
|
||||
multi: true
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should transfer read only collaborator projects", ->
|
||||
@Project.update
|
||||
.calledWith({
|
||||
readOnly_refs: @from_user_id
|
||||
}, {
|
||||
$addToSet: { readOnly_refs: @to_user_id }
|
||||
}, {
|
||||
multi: true
|
||||
})
|
||||
.should.equal true
|
||||
@Project.update
|
||||
.calledWith({
|
||||
readOnly_refs: @from_user_id
|
||||
}, {
|
||||
$pull: { readOnly_refs: @from_user_id }
|
||||
}, {
|
||||
multi: true
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should flush each project to the TPDS", ->
|
||||
for project in @projects
|
||||
@ProjectEntityHandler.flushProjectToThirdPartyDataStore
|
||||
.calledWith(project._id)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "when flushing to TPDS fails", ->
|
||||
beforeEach ->
|
||||
@ProjectEntityHandler.flushProjectToThirdPartyDataStore = sinon.stub().yields(new Error('oops'))
|
||||
@CollaboratorHandler.transferProjects @from_user_id, @to_user_id, @callback
|
||||
|
||||
it "should log an error", ->
|
||||
@logger.err.called.should.equal true
|
||||
|
||||
it "should not return an error since it happens in the background", ->
|
||||
@callback.called.should.equal true
|
||||
@callback.calledWith(new Error('oops')).should.equal false
|
||||
|
||||
@@ -33,7 +33,7 @@ describe "ClsiManager", ->
|
||||
getProjectDocsIfMatch: sinon.stub().callsArgWith(2,null,null)
|
||||
"./ClsiCookieManager": @ClsiCookieManager
|
||||
"./ClsiStateManager": @ClsiStateManager
|
||||
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() }
|
||||
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), err: sinon.stub(), warn: sinon.stub() }
|
||||
"request": @request = sinon.stub()
|
||||
"./ClsiFormatChecker": @ClsiFormatChecker
|
||||
"metrics-sharelatex": @Metrics =
|
||||
@@ -122,6 +122,21 @@ describe "ClsiManager", ->
|
||||
it "should call the callback with a success status", ->
|
||||
@callback.calledWith(null, @status, ).should.equal true
|
||||
|
||||
describe "when the resources fail the precompile check", ->
|
||||
beforeEach ->
|
||||
@ClsiFormatChecker.checkRecoursesForProblems = sinon.stub().callsArgWith(1, new Error("failed"))
|
||||
@ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
|
||||
compile:
|
||||
status: @status = "failure"
|
||||
})
|
||||
@ClsiManager.sendRequest @project_id, @user_id, {}, @callback
|
||||
|
||||
it "should call the callback only once", ->
|
||||
@callback.calledOnce.should.equal true
|
||||
|
||||
it "should call the callback with an error", ->
|
||||
@callback.calledWithExactly(new Error("failed")).should.equal true
|
||||
|
||||
describe "deleteAuxFiles", ->
|
||||
beforeEach ->
|
||||
@ClsiManager._makeRequest = sinon.stub().callsArg(2)
|
||||
@@ -247,12 +262,12 @@ describe "ClsiManager", ->
|
||||
.calledWith(@project_id, {compiler:1, rootDoc_id: 1, imageName: 1, rootFolder: 1})
|
||||
.should.equal true
|
||||
|
||||
it "should flush the project to the database", ->
|
||||
it "should not explicitly flush the project to the database", ->
|
||||
@DocumentUpdaterHandler.flushProjectToMongo
|
||||
.calledWith(@project_id)
|
||||
.should.equal true
|
||||
.should.equal false
|
||||
|
||||
it "should get only the live docs from the docupdater", ->
|
||||
it "should get only the live docs from the docupdater with a background flush in docupdater", ->
|
||||
@DocumentUpdaterHandler.getProjectDocsIfMatch
|
||||
.calledWith(@project_id)
|
||||
.should.equal true
|
||||
@@ -331,7 +346,49 @@ describe "ClsiManager", ->
|
||||
|
||||
it "should set to main.tex", ->
|
||||
@request.compile.rootResourcePath.should.equal "main.tex"
|
||||
|
||||
|
||||
describe "when there is no valid root document and no main.tex document", ->
|
||||
beforeEach () ->
|
||||
@project.rootDoc_id = "not-valid"
|
||||
@docs = {
|
||||
"/other.tex": @doc_1 = {
|
||||
name: "other.tex"
|
||||
_id: "mock-doc-id-1"
|
||||
lines: ["Hello", "world"]
|
||||
},
|
||||
"/chapters/chapter1.tex": @doc_2 = {
|
||||
name: "chapter1.tex"
|
||||
_id: "mock-doc-id-2"
|
||||
lines: [
|
||||
"Chapter 1"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @docs)
|
||||
@ClsiManager._buildRequest @project, null, @callback
|
||||
|
||||
it "should report an error", ->
|
||||
@callback.calledWith(new Error("no main file specified")).should.equal true
|
||||
|
||||
|
||||
describe "when there is no valid root document and a single document which is not main.tex", ->
|
||||
beforeEach (done) ->
|
||||
@project.rootDoc_id = "not-valid"
|
||||
@docs = {
|
||||
"/other.tex": @doc_1 = {
|
||||
name: "other.tex"
|
||||
_id: "mock-doc-id-1"
|
||||
lines: ["Hello", "world"]
|
||||
}
|
||||
}
|
||||
@ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @docs)
|
||||
@ClsiManager._buildRequest @project, null, (@error, @request) =>
|
||||
done()
|
||||
|
||||
it "should set io to the only file", ->
|
||||
@request.compile.rootResourcePath.should.equal "other.tex"
|
||||
|
||||
|
||||
describe "with the draft option", ->
|
||||
it "should add the draft option into the request", (done) ->
|
||||
@ClsiManager._buildRequest @project_id, {timeout:100, draft: true}, (error, request) =>
|
||||
|
||||
@@ -44,7 +44,7 @@ describe "CompileManager", ->
|
||||
|
||||
describe "succesfully", ->
|
||||
beforeEach ->
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit = (_, cb)-> cb(null, true)
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit = (isAutoCompile, compileGroup, cb)-> cb(null, true)
|
||||
@CompileManager.compile @project_id, @user_id, {}, @callback
|
||||
|
||||
it "should check the project has not been recently compiled", ->
|
||||
@@ -84,7 +84,7 @@ describe "CompileManager", ->
|
||||
|
||||
describe "when the project has been recently compiled", ->
|
||||
it "should return", (done)->
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit = (_, cb)-> cb(null, true)
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit = (isAutoCompile, compileGroup, cb)-> cb(null, true)
|
||||
@CompileManager._checkIfRecentlyCompiled = sinon.stub().callsArgWith(2, null, true)
|
||||
@CompileManager.compile @project_id, @user_id, {}, (err, status)->
|
||||
status.should.equal "too-recently-compiled"
|
||||
@@ -92,7 +92,7 @@ describe "CompileManager", ->
|
||||
|
||||
describe "should check the rate limit", ->
|
||||
it "should return", (done)->
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon.stub().callsArgWith(1, null, false)
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon.stub().callsArgWith(2, null, false)
|
||||
@CompileManager.compile @project_id, @user_id, {}, (err, status)->
|
||||
status.should.equal "autocompile-backoff"
|
||||
done()
|
||||
@@ -222,14 +222,14 @@ describe "CompileManager", ->
|
||||
describe "_checkIfAutoCompileLimitHasBeenHit", ->
|
||||
|
||||
it "should be able to compile if it is not an autocompile", (done)->
|
||||
@ratelimiter.addCount.callsArgWith(1, null, true)
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit false, (err, canCompile)=>
|
||||
@ratelimiter.addCount.callsArgWith(2, null, true)
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit false, "everyone", (err, canCompile)=>
|
||||
canCompile.should.equal true
|
||||
done()
|
||||
|
||||
it "should be able to compile if rate limit has remianing", (done)->
|
||||
@ratelimiter.addCount.callsArgWith(1, null, true)
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=>
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit true, "everyone", (err, canCompile)=>
|
||||
args = @ratelimiter.addCount.args[0][0]
|
||||
args.throttle.should.equal 25
|
||||
args.subjectName.should.equal "everyone"
|
||||
@@ -240,13 +240,13 @@ describe "CompileManager", ->
|
||||
|
||||
it "should be not able to compile if rate limit has no remianing", (done)->
|
||||
@ratelimiter.addCount.callsArgWith(1, null, false)
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=>
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit true, "everyone", (err, canCompile)=>
|
||||
canCompile.should.equal false
|
||||
done()
|
||||
|
||||
it "should return false if there is an error in the rate limit", (done)->
|
||||
@ratelimiter.addCount.callsArgWith(1, "error")
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=>
|
||||
@CompileManager._checkIfAutoCompileLimitHasBeenHit true, "everyone", (err, canCompile)=>
|
||||
canCompile.should.equal false
|
||||
done()
|
||||
|
||||
|
||||
+7
-7
@@ -265,19 +265,19 @@ describe 'DocumentUpdaterHandler', ->
|
||||
v: @version
|
||||
@docs = [ @doc0, @doc0, @doc0 ]
|
||||
@body = JSON.stringify @docs
|
||||
@request.get = sinon.stub().callsArgWith(1, null, {statusCode: 200}, @body)
|
||||
@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 200}, @body)
|
||||
@handler.getProjectDocsIfMatch @project_id, @project_state_hash, @callback
|
||||
|
||||
it 'should get the documenst from the document updater', ->
|
||||
url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}/doc?state=#{@project_state_hash}"
|
||||
@request.get.calledWith(url).should.equal true
|
||||
url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}/get_and_flush_if_old?state=#{@project_state_hash}"
|
||||
@request.post.calledWith(url).should.equal true
|
||||
|
||||
it "should call the callback with the documents", ->
|
||||
@callback.calledWithExactly(null, @docs).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)
|
||||
@request.post = sinon.stub().callsArgWith(1, @error = new Error("something went wrong"), null, null)
|
||||
@handler.getProjectDocsIfMatch @project_id, @project_state_hash, @callback
|
||||
|
||||
it "should return an error to the callback", ->
|
||||
@@ -285,7 +285,7 @@ describe 'DocumentUpdaterHandler', ->
|
||||
|
||||
describe "when the document updater returns a conflict error code", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, null, { statusCode: 409 }, "Conflict")
|
||||
@request.post = sinon.stub().callsArgWith(1, null, { statusCode: 409 }, "Conflict")
|
||||
@handler.getProjectDocsIfMatch @project_id, @project_state_hash, @callback
|
||||
|
||||
it "should return the callback with no documents", ->
|
||||
@@ -312,7 +312,7 @@ describe 'DocumentUpdaterHandler', ->
|
||||
|
||||
describe "when the document updater API returns an error", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, @error = new Error("something went wrong"), null, null)
|
||||
@request.post = sinon.stub().callsArgWith(1, @error = new Error("something went wrong"), null, null)
|
||||
@handler.getProjectDocsIfMatch @project_id, @project_state_hash, @callback
|
||||
|
||||
it "should return an error to the callback", ->
|
||||
@@ -320,7 +320,7 @@ describe 'DocumentUpdaterHandler', ->
|
||||
|
||||
describe "when the document updater returns a conflict error code", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, null, { statusCode: 409 }, "Conflict")
|
||||
@request.post = sinon.stub().callsArgWith(1, null, { statusCode: 409 }, "Conflict")
|
||||
@handler.getProjectDocsIfMatch @project_id, @project_state_hash, @callback
|
||||
|
||||
it "should return the callback with no documents", ->
|
||||
|
||||
@@ -24,6 +24,7 @@ describe "DocumentController", ->
|
||||
@doc_lines = ["one", "two", "three"]
|
||||
@version = 42
|
||||
@ranges = {"mock": "ranges"}
|
||||
@pathname = '/a/b/c/file.tex'
|
||||
@rev = 5
|
||||
|
||||
describe "getDocument", ->
|
||||
@@ -34,12 +35,12 @@ describe "DocumentController", ->
|
||||
|
||||
describe "when the document exists", ->
|
||||
beforeEach ->
|
||||
@ProjectEntityHandler.getDoc = sinon.stub().callsArgWith(2, null, @doc_lines, @rev, @version, @ranges)
|
||||
@ProjectEntityHandler.getDoc = sinon.stub().callsArgWith(3, null, @doc_lines, @rev, @version, @ranges, @pathname)
|
||||
@DocumentController.getDocument(@req, @res, @next)
|
||||
|
||||
it "should get the document from Mongo", ->
|
||||
@ProjectEntityHandler.getDoc
|
||||
.calledWith(@project_id, @doc_id)
|
||||
.calledWith(@project_id, @doc_id, pathname: true)
|
||||
.should.equal true
|
||||
|
||||
it "should return the document data to the client as JSON", ->
|
||||
@@ -48,10 +49,11 @@ describe "DocumentController", ->
|
||||
lines: @doc_lines
|
||||
version: @version
|
||||
ranges: @ranges
|
||||
pathname: @pathname
|
||||
|
||||
describe "when the document doesn't exist", ->
|
||||
beforeEach ->
|
||||
@ProjectEntityHandler.getDoc = sinon.stub().callsArgWith(2, new Errors.NotFoundError("not found"), null)
|
||||
@ProjectEntityHandler.getDoc = sinon.stub().callsArgWith(3, new Errors.NotFoundError("not found"), null)
|
||||
@DocumentController.getDocument(@req, @res, @next)
|
||||
|
||||
it "should call next with the NotFoundError", ->
|
||||
|
||||
@@ -6,6 +6,7 @@ SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe "HistoryController", ->
|
||||
beforeEach ->
|
||||
@callback = sinon.stub()
|
||||
@user_id = "user-id-123"
|
||||
@AuthenticationController =
|
||||
getLoggedInUserId: sinon.stub().returns(@user_id)
|
||||
@@ -14,46 +15,134 @@ describe "HistoryController", ->
|
||||
"settings-sharelatex": @settings = {}
|
||||
"logger-sharelatex": @logger = {log: sinon.stub(), error: sinon.stub()}
|
||||
"../Authentication/AuthenticationController": @AuthenticationController
|
||||
@settings.apis =
|
||||
trackchanges:
|
||||
enabled: false
|
||||
url: "http://trackchanges.example.com"
|
||||
project_history:
|
||||
url: "http://project_history.example.com"
|
||||
|
||||
describe "proxyToHistoryApi", ->
|
||||
beforeEach ->
|
||||
@req = { url: "/mock/url", method: "POST" }
|
||||
@res = "mock-res"
|
||||
@next = sinon.stub()
|
||||
@settings.apis =
|
||||
trackchanges:
|
||||
url: "http://trackchanges.example.com"
|
||||
@proxy =
|
||||
events: {}
|
||||
pipe: sinon.stub()
|
||||
on: (event, handler) -> @events[event] = handler
|
||||
@request.returns @proxy
|
||||
@HistoryController.proxyToHistoryApi @req, @res, @next
|
||||
|
||||
describe "successfully", ->
|
||||
it "should get the user id", ->
|
||||
@AuthenticationController.getLoggedInUserId
|
||||
.calledWith(@req)
|
||||
.should.equal true
|
||||
describe "with project history enabled", ->
|
||||
beforeEach ->
|
||||
@settings.apis.project_history.enabled = true
|
||||
@HistoryController.proxyToHistoryApi @req, @res, @next
|
||||
|
||||
it "should call the track changes api", ->
|
||||
@request
|
||||
.calledWith({
|
||||
url: "#{@settings.apis.trackchanges.url}#{@req.url}"
|
||||
method: @req.method
|
||||
headers:
|
||||
"X-User-Id": @user_id
|
||||
})
|
||||
.should.equal true
|
||||
it "should get the user id", ->
|
||||
@AuthenticationController.getLoggedInUserId
|
||||
.calledWith(@req)
|
||||
.should.equal true
|
||||
|
||||
it "should pipe the response to the client", ->
|
||||
@proxy.pipe
|
||||
.calledWith(@res)
|
||||
.should.equal true
|
||||
it "should call the project history api", ->
|
||||
@request
|
||||
.calledWith({
|
||||
url: "#{@settings.apis.project_history.url}#{@req.url}"
|
||||
method: @req.method
|
||||
headers:
|
||||
"X-User-Id": @user_id
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should pipe the response to the client", ->
|
||||
@proxy.pipe
|
||||
.calledWith(@res)
|
||||
.should.equal true
|
||||
|
||||
describe "with project history disabled", ->
|
||||
beforeEach ->
|
||||
@settings.apis.project_history.enabled = false
|
||||
@HistoryController.proxyToHistoryApi @req, @res, @next
|
||||
|
||||
it "should call the track changes api", ->
|
||||
@request
|
||||
.calledWith({
|
||||
url: "#{@settings.apis.trackchanges.url}#{@req.url}"
|
||||
method: @req.method
|
||||
headers:
|
||||
"X-User-Id": @user_id
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
describe "with an error", ->
|
||||
beforeEach ->
|
||||
@HistoryController.proxyToHistoryApi @req, @res, @next
|
||||
@proxy.events["error"].call(@proxy, @error = new Error("oops"))
|
||||
|
||||
it "should pass the error up the call chain", ->
|
||||
@next.calledWith(@error).should.equal true
|
||||
|
||||
describe "initializeProject", ->
|
||||
describe "with project history enabled", ->
|
||||
beforeEach ->
|
||||
@settings.apis.project_history.enabled = true
|
||||
|
||||
describe "project history returns a successful response", ->
|
||||
beforeEach ->
|
||||
@overleaf_id = 1234
|
||||
@res = statusCode: 200
|
||||
@body = JSON.stringify(project: id: @overleaf_id)
|
||||
@request.post = sinon.stub().callsArgWith(1, null, @res, @body)
|
||||
|
||||
@HistoryController.initializeProject @callback
|
||||
|
||||
it "should call the project history api", ->
|
||||
@request.post.calledWith(
|
||||
url: "#{@settings.apis.project_history.url}/project"
|
||||
).should.equal true
|
||||
|
||||
it "should return the callback with the overleaf id", ->
|
||||
@callback.calledWithExactly(null, { @overleaf_id }).should.equal true
|
||||
|
||||
describe "project history returns a response without the project id", ->
|
||||
beforeEach ->
|
||||
@res = statusCode: 200
|
||||
@body = JSON.stringify(project: {})
|
||||
@request.post = sinon.stub().callsArgWith(1, null, @res, @body)
|
||||
|
||||
@HistoryController.initializeProject @callback
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback
|
||||
.calledWith(sinon.match.has("message", "project-history did not provide an id"))
|
||||
.should.equal true
|
||||
|
||||
describe "project history returns a unsuccessful response", ->
|
||||
beforeEach ->
|
||||
@res = statusCode: 404
|
||||
@request.post = sinon.stub().callsArgWith(1, null, @res)
|
||||
|
||||
@HistoryController.initializeProject @callback
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback
|
||||
.calledWith(sinon.match.has("message", "project-history returned a non-success status code: 404"))
|
||||
.should.equal true
|
||||
|
||||
describe "project history errors", ->
|
||||
beforeEach ->
|
||||
@error = sinon.stub()
|
||||
@request.post = sinon.stub().callsArgWith(1, @error)
|
||||
|
||||
@HistoryController.initializeProject @callback
|
||||
|
||||
it "should return the callback with the error", ->
|
||||
@callback.calledWithExactly(@error).should.equal true
|
||||
|
||||
describe "with project history disabled", ->
|
||||
beforeEach ->
|
||||
@settings.apis.project_history.enabled = false
|
||||
@HistoryController.initializeProject @callback
|
||||
|
||||
it "should return the callback", ->
|
||||
@callback.calledWithExactly().should.equal true
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
chai = require('chai')
|
||||
expect = chai.expect
|
||||
chai.should()
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../../app/js/Features/History/HistoryManager"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe "HistoryManager", ->
|
||||
beforeEach ->
|
||||
@HistoryManager = SandboxedModule.require modulePath, requires:
|
||||
"request" : @request = sinon.stub()
|
||||
"settings-sharelatex": @settings =
|
||||
apis:
|
||||
trackchanges:
|
||||
url: "trackchanges.sharelatex.com"
|
||||
"logger-sharelatex": @logger = {log: sinon.stub(), error: sinon.stub()}
|
||||
@project_id = "project-id-123"
|
||||
@callback = sinon.stub()
|
||||
@request.post = sinon.stub()
|
||||
|
||||
describe "flushProject", ->
|
||||
describe "with a successful response code", ->
|
||||
beforeEach ->
|
||||
@request.post = sinon.stub().callsArgWith(1, null, statusCode: 204, "")
|
||||
@HistoryManager.flushProject @project_id, @callback
|
||||
|
||||
it "should flush the project in the track changes api", ->
|
||||
@request.post
|
||||
.calledWith("#{@settings.apis.trackchanges.url}/project/#{@project_id}/flush")
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback without an error", ->
|
||||
@callback.calledWith(null).should.equal true
|
||||
|
||||
describe "with a failed response code", ->
|
||||
beforeEach ->
|
||||
@request.post = sinon.stub().callsArgWith(1, null, statusCode: 500, "")
|
||||
@HistoryManager.flushProject @project_id, @callback
|
||||
|
||||
it "should call the callback with an error", ->
|
||||
@callback.calledWith(new Error("track-changes api responded with a non-success code: 500")).should.equal true
|
||||
|
||||
it "should log the error", ->
|
||||
@logger.error
|
||||
.calledWith({
|
||||
err: new Error("track-changes api responded with a non-success code: 500")
|
||||
project_id: @project_id
|
||||
}, "error flushing project in track-changes api")
|
||||
.should.equal true
|
||||
|
||||
describe "ArchiveProject", ->
|
||||
|
||||
it "should call the post endpoint", (done)->
|
||||
@request.post.callsArgWith(1, null, {})
|
||||
@HistoryManager.archiveProject @project_id, (err)=>
|
||||
@request.post.calledWith("#{@settings.apis.trackchanges.url}/project/#{@project_id}/archive")
|
||||
done()
|
||||
|
||||
it "should return an error on a non success", (done)->
|
||||
@request.post.callsArgWith(1, null, {statusCode:500})
|
||||
@HistoryManager.archiveProject @project_id, (err)=>
|
||||
expect(err).to.exist
|
||||
done()
|
||||
@@ -22,6 +22,8 @@ describe 'ProjectCreationHandler', ->
|
||||
@._id = project_id
|
||||
@owner_ref = options.owner_ref
|
||||
@name = options.name
|
||||
@overleaf =
|
||||
history: {}
|
||||
save: sinon.stub().callsArg(0)
|
||||
rootFolder:[{
|
||||
_id: rootFolderId
|
||||
@@ -36,11 +38,13 @@ describe 'ProjectCreationHandler', ->
|
||||
setRootDoc: sinon.stub().callsArg(2)
|
||||
@ProjectDetailsHandler =
|
||||
validateProjectName: sinon.stub().yields()
|
||||
@HistoryController =
|
||||
initializeProject: sinon.stub().callsArg(0)
|
||||
|
||||
@user =
|
||||
@user =
|
||||
first_name:"first name here"
|
||||
last_name:"last name here"
|
||||
ace:
|
||||
ace:
|
||||
spellCheckLanguage:"de"
|
||||
|
||||
@User = findById:sinon.stub().callsArgWith(2, null, @user)
|
||||
@@ -49,6 +53,7 @@ describe 'ProjectCreationHandler', ->
|
||||
'../../models/User': User:@User
|
||||
'../../models/Project':{Project:@ProjectModel}
|
||||
'../../models/Folder':{Folder:@FolderModel}
|
||||
'../History/HistoryController': @HistoryController
|
||||
'./ProjectEntityHandler':@ProjectEntityHandler
|
||||
"./ProjectDetailsHandler":@ProjectDetailsHandler
|
||||
"settings-sharelatex": @Settings = {}
|
||||
@@ -60,32 +65,48 @@ describe 'ProjectCreationHandler', ->
|
||||
|
||||
describe 'Creating a Blank project', ->
|
||||
beforeEach ->
|
||||
@overleaf_id = 1234
|
||||
@HistoryController.initializeProject = sinon.stub().callsArgWith(0, null, { @overleaf_id })
|
||||
@ProjectModel::save = sinon.stub().callsArg(0)
|
||||
|
||||
describe "successfully", ->
|
||||
|
||||
it "should save the project", (done)->
|
||||
@handler.createBlankProject ownerId, projectName, =>
|
||||
@ProjectModel::save.called.should.equal true
|
||||
done()
|
||||
|
||||
|
||||
it "should return the project in the callback", (done)->
|
||||
@handler.createBlankProject ownerId, projectName, (err, project)->
|
||||
project.name.should.equal projectName
|
||||
(project.owner_ref + "").should.equal ownerId
|
||||
done()
|
||||
|
||||
it "should initialize the project overleaf if history id not provided", (done)->
|
||||
@handler.createBlankProject ownerId, projectName, done
|
||||
@HistoryController.initializeProject.calledWith().should.equal true
|
||||
|
||||
it "should set the overleaf id if overleaf id not provided", (done)->
|
||||
@handler.createBlankProject ownerId, projectName, (err, project)=>
|
||||
project.overleaf.history.id.should.equal @overleaf_id
|
||||
done()
|
||||
|
||||
it "should set the overleaf id if overleaf id provided", (done)->
|
||||
overleaf_id = 2345
|
||||
@handler.createBlankProject ownerId, projectName, overleaf_id, (err, project)->
|
||||
project.overleaf.history.id.should.equal overleaf_id
|
||||
done()
|
||||
|
||||
it "should set the language from the user", (done)->
|
||||
@handler.createBlankProject ownerId, projectName, (err, project)->
|
||||
project.spellCheckLanguage.should.equal "de"
|
||||
done()
|
||||
|
||||
|
||||
it "should set the imageName to currentImageName if set", (done) ->
|
||||
@Settings.currentImageName = "mock-image-name"
|
||||
@handler.createBlankProject ownerId, projectName, (err, project)=>
|
||||
project.imageName.should.equal @Settings.currentImageName
|
||||
done()
|
||||
|
||||
|
||||
it "should not set the imageName if no currentImageName", (done) ->
|
||||
@Settings.currentImageName = null
|
||||
@handler.createBlankProject ownerId, projectName, (err, project)=>
|
||||
@@ -96,21 +117,21 @@ describe 'ProjectCreationHandler', ->
|
||||
beforeEach ->
|
||||
@ProjectModel::save = sinon.stub().callsArgWith(0, new Error("something went wrong"))
|
||||
@handler.createBlankProject ownerId, projectName, @callback
|
||||
|
||||
|
||||
it 'should return the error to the callback', ->
|
||||
should.exist @callback.args[0][0]
|
||||
|
||||
|
||||
describe "with an invalid name", ->
|
||||
beforeEach ->
|
||||
@ProjectDetailsHandler.validateProjectName = sinon.stub().yields(new Error("bad name"))
|
||||
@handler.createBlankProject ownerId, projectName, @callback
|
||||
|
||||
|
||||
it 'should return the error to the callback', ->
|
||||
should.exist @callback.args[0][0]
|
||||
|
||||
|
||||
it 'should not try to create the project', ->
|
||||
@ProjectModel::save.called.should.equal false
|
||||
|
||||
|
||||
|
||||
describe 'Creating a basic project', ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -12,7 +12,7 @@ describe 'ProjectDetailsHandler', ->
|
||||
beforeEach ->
|
||||
@project_id = "321l3j1kjkjl"
|
||||
@user_id = "user-id-123"
|
||||
@project =
|
||||
@project =
|
||||
name: "project"
|
||||
description: "this is a great project"
|
||||
something:"should not exist"
|
||||
@@ -20,7 +20,7 @@ describe 'ProjectDetailsHandler', ->
|
||||
owner_ref: @user_id
|
||||
@user =
|
||||
features: "mock-features"
|
||||
@ProjectGetter =
|
||||
@ProjectGetter =
|
||||
getProjectWithoutDocLines: sinon.stub().callsArgWith(1, null, @project)
|
||||
getProject: sinon.stub().callsArgWith(2, null, @project)
|
||||
@ProjectModel =
|
||||
@@ -43,7 +43,7 @@ describe 'ProjectDetailsHandler', ->
|
||||
describe "getDetails", ->
|
||||
|
||||
it "should find the project and owner", (done)->
|
||||
@handler.getDetails @project_id, (err, details)=>
|
||||
@handler.getDetails @project_id, (err, details)=>
|
||||
details.name.should.equal @project.name
|
||||
details.description.should.equal @project.description
|
||||
details.compiler.should.equal @project.compiler
|
||||
@@ -51,6 +51,13 @@ describe 'ProjectDetailsHandler', ->
|
||||
assert.equal(details.something, undefined)
|
||||
done()
|
||||
|
||||
it "should find overleaf metadata if it exists", (done)->
|
||||
@project.overleaf = { id: 'id' }
|
||||
@handler.getDetails @project_id, (err, details)=>
|
||||
details.overleaf.should.equal @project.overleaf
|
||||
assert.equal(details.something, undefined)
|
||||
done()
|
||||
|
||||
it "should return an error for a non-existent project", (done)->
|
||||
@ProjectGetter.getProject.callsArg(2, null, null)
|
||||
err = new Errors.NotFoundError("project not found")
|
||||
@@ -80,7 +87,7 @@ describe 'ProjectDetailsHandler', ->
|
||||
@handler.getProjectDescription @project_id, (returnedErr, returnedDescription)=>
|
||||
err.should.equal returnedErr
|
||||
description.should.equal returnedDescription
|
||||
done()
|
||||
done()
|
||||
|
||||
describe "setProjectDescription", ->
|
||||
|
||||
@@ -111,7 +118,7 @@ describe 'ProjectDetailsHandler', ->
|
||||
@handler.renameProject @project_id, @newName, =>
|
||||
@tpdsUpdateSender.moveEntity.calledWith({project_id:@project_id, project_name:@project.name, newProjectName:@newName}).should.equal true
|
||||
done()
|
||||
|
||||
|
||||
it "should not do anything with an invalid name", (done) ->
|
||||
@handler.validateProjectName = sinon.stub().yields(new Error("invalid name"))
|
||||
@handler.renameProject @project_id, @newName, =>
|
||||
@@ -120,6 +127,12 @@ describe 'ProjectDetailsHandler', ->
|
||||
done()
|
||||
|
||||
describe "validateProjectName", ->
|
||||
|
||||
it "should reject undefined names", (done) ->
|
||||
@handler.validateProjectName undefined, (error) ->
|
||||
expect(error).to.exist
|
||||
done()
|
||||
|
||||
it "should reject empty names", (done) ->
|
||||
@handler.validateProjectName "", (error) ->
|
||||
expect(error).to.exist
|
||||
|
||||
@@ -14,17 +14,17 @@ describe 'ProjectEntityHandler', ->
|
||||
doc_id = '4eecb1c1bffa66588e0000a2'
|
||||
folder_id = "4eecaffcbffa66588e000008"
|
||||
rootFolderId = "4eecaffcbffa66588e000007"
|
||||
|
||||
|
||||
beforeEach ->
|
||||
@FileStoreHandler =
|
||||
@FileStoreHandler =
|
||||
uploadFileFromDisk:(project_id, fileRef, localImagePath, callback)->callback()
|
||||
copyFile: sinon.stub().callsArgWith(4, null)
|
||||
@tpdsUpdateSender =
|
||||
addDoc:sinon.stub().callsArg(1)
|
||||
addFile:sinon.stub().callsArg(1)
|
||||
addFolder:sinon.stub().callsArg(1)
|
||||
@rootFolder =
|
||||
_id:rootFolderId,
|
||||
@rootFolder =
|
||||
_id:rootFolderId,
|
||||
folders:[
|
||||
{name:"level1", folders:[]}
|
||||
]
|
||||
@@ -46,7 +46,7 @@ describe 'ProjectEntityHandler', ->
|
||||
@FileModel = class File
|
||||
constructor:(options)->
|
||||
{@name} = options
|
||||
@._id = "file_id"
|
||||
@._id = "file_id"
|
||||
@rev = 0
|
||||
@FolderModel = class Folder
|
||||
constructor:(options)->
|
||||
@@ -57,12 +57,12 @@ describe 'ProjectEntityHandler', ->
|
||||
|
||||
@ProjectModel.findById = (project_id, callback)=> callback(null, @project)
|
||||
@ProjectModel.getProject = (project_id, fields, callback)=> callback(null, @project)
|
||||
@ProjectGetter =
|
||||
@ProjectGetter =
|
||||
getProjectWithOnlyFolders : (project_id, callback)=> callback(null, @project)
|
||||
getProjectWithoutDocLines : (project_id, callback)=> callback(null, @project)
|
||||
getProject:sinon.stub()
|
||||
@projectUpdater = markAsUpdated:sinon.stub()
|
||||
@projectLocator =
|
||||
@projectLocator =
|
||||
findElement : sinon.stub()
|
||||
@settings =
|
||||
maxEntitiesPerProject:200
|
||||
@@ -97,8 +97,8 @@ describe 'ProjectEntityHandler', ->
|
||||
else
|
||||
cb null, @parentFolder
|
||||
@ProjectEntityHandler.addFolder = (project_id, parentFolder_id, folderName, callback)=>
|
||||
callback null, {name:folderName}, @parentFolder_id
|
||||
|
||||
callback null, {name:folderName}, @parentFolder_id
|
||||
|
||||
it 'should return the root folder if the path is just a slash', (done)->
|
||||
path = "/"
|
||||
@ProjectEntityHandler.mkdirp project_id, path, (err, folders, lastFolder)=>
|
||||
@@ -239,7 +239,7 @@ describe 'ProjectEntityHandler', ->
|
||||
@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, path: @pathAfterMove)
|
||||
@ProjectGetter.getProject.callsArgWith(2, null, @project)
|
||||
@tpdsUpdateSender.moveEntity = sinon.stub().callsArg(1)
|
||||
|
||||
|
||||
describe "moving a doc", ->
|
||||
beforeEach (done) ->
|
||||
@docId = "4eecaffcbffa66588e000009"
|
||||
@@ -257,10 +257,10 @@ describe 'ProjectEntityHandler', ->
|
||||
it 'should remove the element from its current position', ->
|
||||
@ProjectEntityHandler._removeElementFromMongoArray
|
||||
.calledWith(@ProjectModel, project_id, @path.mongo ).should.equal true
|
||||
|
||||
|
||||
it "should put the element back in the new folder", ->
|
||||
@ProjectEntityHandler._putElement.calledWith(@project, folder_id, @doc, "docs").should.equal true
|
||||
|
||||
|
||||
it 'should tell the third party data store', ->
|
||||
@tpdsUpdateSender.moveEntity
|
||||
.calledWith({
|
||||
@@ -271,7 +271,7 @@ describe 'ProjectEntityHandler', ->
|
||||
rev: @doc.rev
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
|
||||
describe "moving a folder", ->
|
||||
beforeEach ->
|
||||
@folder_id = "folder-to-move"
|
||||
@@ -294,7 +294,7 @@ describe 'ProjectEntityHandler', ->
|
||||
else
|
||||
console.log "UNKNOWN ID", options
|
||||
sinon.spy @projectLocator, "findElement"
|
||||
|
||||
|
||||
describe "when the destination folder is outside the moving folder", ->
|
||||
beforeEach (done) ->
|
||||
@path.fileSystem = "/one/directory"
|
||||
@@ -318,7 +318,7 @@ describe 'ProjectEntityHandler', ->
|
||||
@path.mongo
|
||||
)
|
||||
.should.equal true
|
||||
|
||||
|
||||
it "should put the element back in the new folder", ->
|
||||
@ProjectEntityHandler._putElement
|
||||
.calledWith(
|
||||
@@ -328,7 +328,7 @@ describe 'ProjectEntityHandler', ->
|
||||
"folder"
|
||||
)
|
||||
.should.equal true
|
||||
|
||||
|
||||
it 'should tell the third party data store', ->
|
||||
@tpdsUpdateSender.moveEntity
|
||||
.calledWith({
|
||||
@@ -339,7 +339,7 @@ describe 'ProjectEntityHandler', ->
|
||||
rev: @folder.rev
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
|
||||
describe "when the destination folder is inside the moving folder", ->
|
||||
beforeEach ->
|
||||
@path.fileSystem = "/one/two"
|
||||
@@ -355,7 +355,7 @@ describe 'ProjectEntityHandler', ->
|
||||
project: @project
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
|
||||
it "should return an error", ->
|
||||
@callback
|
||||
.calledWith(new Error("destination folder is a child folder of me"))
|
||||
@@ -385,16 +385,41 @@ describe 'ProjectEntityHandler', ->
|
||||
@rev = 5
|
||||
@version = 42
|
||||
@ranges = {"mock": "ranges"}
|
||||
|
||||
@DocstoreManager.getDoc = sinon.stub().callsArgWith(3, null, @lines, @rev, @version, @ranges)
|
||||
@ProjectEntityHandler.getDoc project_id, doc_id, @callback
|
||||
|
||||
it "should call the docstore", ->
|
||||
@DocstoreManager.getDoc
|
||||
.calledWith(project_id, doc_id)
|
||||
.should.equal true
|
||||
describe 'without pathname option', ->
|
||||
beforeEach ->
|
||||
@ProjectEntityHandler.getDoc project_id, doc_id, @callback
|
||||
|
||||
it "should call the docstore", ->
|
||||
@DocstoreManager.getDoc
|
||||
.calledWith(project_id, doc_id)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback with the lines, version and rev", ->
|
||||
@callback.calledWith(null, @lines, @rev, @version, @ranges).should.equal true
|
||||
|
||||
describe 'with pathname option', ->
|
||||
beforeEach ->
|
||||
@project = 'a project'
|
||||
@path = mongo: "mongo.path", fileSystem: "/file/system/path"
|
||||
@projectLocator.findElement = sinon.stub().callsArgWith(1, null, {}, @path)
|
||||
@ProjectEntityHandler.getDoc project_id, doc_id, {pathname: true}, @callback
|
||||
|
||||
it "should call the project locator", ->
|
||||
@projectLocator.findElement
|
||||
.calledWith({project_id: project_id, element_id: doc_id, type: 'doc'})
|
||||
.should.equal true
|
||||
|
||||
it "should call the docstore", ->
|
||||
@DocstoreManager.getDoc
|
||||
.calledWith(project_id, doc_id)
|
||||
.should.equal true
|
||||
|
||||
it "should return the pathname if option given", ->
|
||||
@callback.calledWith(null, @lines, @rev, @version, @ranges, @path.fileSystem).should.equal true
|
||||
|
||||
it "should call the callback with the lines, version and rev", ->
|
||||
@callback.calledWith(null, @lines, @rev, @version, @ranges).should.equal true
|
||||
|
||||
describe 'addDoc', ->
|
||||
beforeEach ->
|
||||
@@ -876,7 +901,7 @@ describe 'ProjectEntityHandler', ->
|
||||
path: path
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
|
||||
describe "setRootDoc", ->
|
||||
it "should call Project.update", ->
|
||||
@project_id = "project-id-123234adfs"
|
||||
@@ -907,22 +932,22 @@ describe 'ProjectEntityHandler', ->
|
||||
|
||||
it 'should copy the file in FileStoreHandler', (done)->
|
||||
@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:"somehintg"}})
|
||||
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)=>
|
||||
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)=>
|
||||
@FileStoreHandler.copyFile.calledWith(oldProject_id, oldFileRef._id, project_id, fileRef._id).should.equal true
|
||||
done()
|
||||
|
||||
it 'should put file into folder by calling put element', (done)->
|
||||
@ProjectEntityHandler._putElement = (passedProject, passedFolder_id, passedFileRef, passedType, callback)->
|
||||
@ProjectEntityHandler._putElement = (passedProject, passedFolder_id, passedFileRef, passedType, callback)->
|
||||
passedProject._id.should.equal project_id
|
||||
passedFolder_id.should.equal folder_id
|
||||
passedFileRef.name.should.equal fileName
|
||||
passedType.should.equal 'file'
|
||||
done()
|
||||
|
||||
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)->
|
||||
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)->
|
||||
|
||||
it 'should return doc and parent folder', (done)->
|
||||
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)->
|
||||
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)->
|
||||
parentFolder.should.equal folder_id
|
||||
fileRef.name.should.equal fileName
|
||||
done()
|
||||
@@ -942,7 +967,7 @@ describe 'ProjectEntityHandler', ->
|
||||
options.rev.should.equal 0
|
||||
done()
|
||||
|
||||
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)->
|
||||
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)->
|
||||
|
||||
|
||||
describe "renameEntity", ->
|
||||
@@ -1054,7 +1079,7 @@ describe 'ProjectEntityHandler', ->
|
||||
@folder =
|
||||
_id: ObjectId()
|
||||
name: "someFolder"
|
||||
@doc =
|
||||
@doc =
|
||||
_id: ObjectId()
|
||||
name: "new.tex"
|
||||
@path = mongo: "mongo.path", fileSystem: "/file/system/old.tex"
|
||||
@@ -1064,7 +1089,7 @@ describe 'ProjectEntityHandler', ->
|
||||
|
||||
|
||||
describe "updating the project", ->
|
||||
|
||||
|
||||
|
||||
it "should use the correct mongo path", (done)->
|
||||
@ProjectEntityHandler._putElement @project, @folder._id, @doc, "docs", (err)=>
|
||||
@@ -1089,12 +1114,12 @@ describe 'ProjectEntityHandler', ->
|
||||
done()
|
||||
|
||||
it "should error if the element has no _id", (done)->
|
||||
doc =
|
||||
doc =
|
||||
name:"something"
|
||||
@ProjectEntityHandler._putElement @project, @folder._id, doc, "doc", (err)=>
|
||||
@ProjectModel.update.called.should.equal false
|
||||
done()
|
||||
|
||||
|
||||
|
||||
|
||||
describe "_countElements", ->
|
||||
@@ -1109,7 +1134,7 @@ describe 'ProjectEntityHandler', ->
|
||||
fileRefs:{}
|
||||
folders: [
|
||||
{
|
||||
docs:[_id:1234],
|
||||
docs:[_id:1234],
|
||||
fileRefs:[{_id:23123}, {_id:123213}, {_id:2312}]
|
||||
folders:[
|
||||
{
|
||||
@@ -1131,7 +1156,7 @@ describe 'ProjectEntityHandler', ->
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
it "should return the correct number", (done)->
|
||||
@ProjectEntityHandler._countElements @project, (err, count)->
|
||||
@@ -1142,19 +1167,19 @@ describe 'ProjectEntityHandler', ->
|
||||
@project.rootFolder[0].folders[0].folders = undefined
|
||||
@ProjectEntityHandler._countElements @project, (err, count)->
|
||||
count.should.equal 17
|
||||
done()
|
||||
done()
|
||||
|
||||
it "should deal with null docs", (done)->
|
||||
@project.rootFolder[0].folders[0].docs = undefined
|
||||
@ProjectEntityHandler._countElements @project, (err, count)->
|
||||
count.should.equal 23
|
||||
done()
|
||||
done()
|
||||
|
||||
it "should deal with null fileRefs", (done)->
|
||||
@project.rootFolder[0].folders[0].folders[0].fileRefs = undefined
|
||||
@ProjectEntityHandler._countElements @project, (err, count)->
|
||||
count.should.equal 23
|
||||
done()
|
||||
done()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,24 +10,22 @@ describe "ArchiveManager", ->
|
||||
beforeEach ->
|
||||
@logger =
|
||||
error: sinon.stub()
|
||||
warn: sinon.stub()
|
||||
err:->
|
||||
log: sinon.stub()
|
||||
@process = new events.EventEmitter
|
||||
@process.stdout = new events.EventEmitter
|
||||
@process.stderr = new events.EventEmitter
|
||||
|
||||
@child =
|
||||
spawn: sinon.stub().returns(@process)
|
||||
|
||||
|
||||
@metrics =
|
||||
Timer: class Timer
|
||||
done: sinon.stub()
|
||||
@zipfile = new events.EventEmitter
|
||||
@zipfile.readEntry = sinon.stub()
|
||||
@zipfile.close = sinon.stub()
|
||||
|
||||
@ArchiveManager = SandboxedModule.require modulePath, requires:
|
||||
"child_process": @child
|
||||
"yauzl": @yauzl = {open: sinon.stub().callsArgWith(2, null, @zipfile)}
|
||||
"logger-sharelatex": @logger
|
||||
"metrics-sharelatex": @metrics
|
||||
"fs": @fs = {}
|
||||
"fs-extra": @fse = {}
|
||||
|
||||
describe "extractZipArchive", ->
|
||||
beforeEach ->
|
||||
@@ -39,10 +37,10 @@ describe "ArchiveManager", ->
|
||||
describe "successfully", ->
|
||||
beforeEach (done) ->
|
||||
@ArchiveManager.extractZipArchive @source, @destination, done
|
||||
@process.emit "close"
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should run unzip", ->
|
||||
@child.spawn.calledWithExactly("unzip", [@source, "-d", @destination]).should.equal true
|
||||
it "should run yauzl", ->
|
||||
@yauzl.open.calledWith(@source).should.equal true
|
||||
|
||||
it "should time the unzip", ->
|
||||
@metrics.Timer::done.called.should.equal true
|
||||
@@ -50,13 +48,12 @@ describe "ArchiveManager", ->
|
||||
it "should log the unzip", ->
|
||||
@logger.log.calledWith(sinon.match.any, "unzipping file").should.equal true
|
||||
|
||||
describe "with an error on stderr", ->
|
||||
describe "with an error in the zip file header", ->
|
||||
beforeEach (done) ->
|
||||
@yauzl.open = sinon.stub().callsArgWith(2, new Error("Something went wrong"))
|
||||
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
|
||||
@callback(error)
|
||||
done()
|
||||
@process.stderr.emit "data", "Something went wrong"
|
||||
@process.emit "close"
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback.calledWithExactly(new Error("Something went wrong")).should.equal true
|
||||
@@ -74,60 +71,177 @@ describe "ArchiveManager", ->
|
||||
it "should return the callback with an error", ->
|
||||
@callback.calledWithExactly(new Error("zip_too_large")).should.equal true
|
||||
|
||||
it "should not call spawn", ->
|
||||
@child.spawn.called.should.equal false
|
||||
it "should not call yauzl.open", ->
|
||||
@yauzl.open.called.should.equal false
|
||||
|
||||
describe "with an error on the process", ->
|
||||
describe "with an error in the extracted files", ->
|
||||
beforeEach (done) ->
|
||||
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
|
||||
@callback(error)
|
||||
done()
|
||||
@process.emit "error", new Error("Something went wrong")
|
||||
@zipfile.emit "error", new Error("Something went wrong")
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback.calledWithExactly(new Error("Something went wrong")).should.equal true
|
||||
|
||||
it "should log out the error", ->
|
||||
@logger.error.called.should.equal true
|
||||
|
||||
|
||||
describe "with a relative extracted file path", ->
|
||||
beforeEach (done) ->
|
||||
@zipfile.openReadStream = sinon.stub()
|
||||
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
|
||||
@callback(error)
|
||||
done()
|
||||
@zipfile.emit "entry", {fileName: "../testfile.txt"}
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should not write try to read the file entry", ->
|
||||
@zipfile.openReadStream.called.should.equal false
|
||||
|
||||
it "should log out a warning", ->
|
||||
@logger.warn.called.should.equal true
|
||||
|
||||
describe "with an unnormalized extracted file path", ->
|
||||
beforeEach (done) ->
|
||||
@zipfile.openReadStream = sinon.stub()
|
||||
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
|
||||
@callback(error)
|
||||
done()
|
||||
@zipfile.emit "entry", {fileName: "foo/./testfile.txt"}
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should not write try to read the file entry", ->
|
||||
@zipfile.openReadStream.called.should.equal false
|
||||
|
||||
it "should log out a warning", ->
|
||||
@logger.warn.called.should.equal true
|
||||
|
||||
describe "with a directory entry", ->
|
||||
beforeEach (done) ->
|
||||
@zipfile.openReadStream = sinon.stub()
|
||||
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
|
||||
@callback(error)
|
||||
done()
|
||||
@zipfile.emit "entry", {fileName: "testdir/"}
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should not write try to read the entry", ->
|
||||
@zipfile.openReadStream.called.should.equal false
|
||||
|
||||
it "should not log out a warning", ->
|
||||
@logger.warn.called.should.equal false
|
||||
|
||||
describe "with an error opening the file read stream", ->
|
||||
beforeEach (done) ->
|
||||
@zipfile.openReadStream = sinon.stub().callsArgWith(1, new Error("Something went wrong"))
|
||||
@writeStream = new events.EventEmitter
|
||||
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
|
||||
@callback(error)
|
||||
done()
|
||||
@zipfile.emit "entry", {fileName: "testfile.txt"}
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback.calledWithExactly(new Error("Something went wrong")).should.equal true
|
||||
|
||||
it "should log out the error", ->
|
||||
@logger.error.called.should.equal true
|
||||
|
||||
it "should close the zipfile", ->
|
||||
@zipfile.close.called.should.equal true
|
||||
|
||||
describe "with an error in the file read stream", ->
|
||||
beforeEach (done) ->
|
||||
@readStream = new events.EventEmitter
|
||||
@readStream.pipe = sinon.stub()
|
||||
@zipfile.openReadStream = sinon.stub().callsArgWith(1, null, @readStream)
|
||||
@writeStream = new events.EventEmitter
|
||||
@fs.createWriteStream = sinon.stub().returns @writeStream
|
||||
@fse.ensureDir = sinon.stub().callsArg(1)
|
||||
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
|
||||
@callback(error)
|
||||
done()
|
||||
@zipfile.emit "entry", {fileName: "testfile.txt"}
|
||||
@readStream.emit "error", new Error("Something went wrong")
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback.calledWithExactly(new Error("Something went wrong")).should.equal true
|
||||
|
||||
it "should log out the error", ->
|
||||
@logger.error.called.should.equal true
|
||||
|
||||
it "should close the zipfile", ->
|
||||
@zipfile.close.called.should.equal true
|
||||
|
||||
describe "with an error in the file write stream", ->
|
||||
beforeEach (done) ->
|
||||
@readStream = new events.EventEmitter
|
||||
@readStream.pipe = sinon.stub()
|
||||
@readStream.unpipe = sinon.stub()
|
||||
@readStream.destroy = sinon.stub()
|
||||
@zipfile.openReadStream = sinon.stub().callsArgWith(1, null, @readStream)
|
||||
@writeStream = new events.EventEmitter
|
||||
@fs.createWriteStream = sinon.stub().returns @writeStream
|
||||
@fse.ensureDir = sinon.stub().callsArg(1)
|
||||
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
|
||||
@callback(error)
|
||||
done()
|
||||
@zipfile.emit "entry", {fileName: "testfile.txt"}
|
||||
@writeStream.emit "error", new Error("Something went wrong")
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback.calledWithExactly(new Error("Something went wrong")).should.equal true
|
||||
|
||||
it "should log out the error", ->
|
||||
@logger.error.called.should.equal true
|
||||
|
||||
it "should unpipe from the readstream", ->
|
||||
@readStream.unpipe.called.should.equal true
|
||||
|
||||
it "should destroy the readstream", ->
|
||||
@readStream.destroy.called.should.equal true
|
||||
|
||||
it "should close the zipfile", ->
|
||||
@zipfile.close.called.should.equal true
|
||||
|
||||
describe "_isZipTooLarge", ->
|
||||
beforeEach ->
|
||||
@output = (totalSize)->" Length Date Time Name \n-------- ---- ---- ---- \n241 03-12-16 12:20 main.tex \n108801 03-12-16 12:20 ddd/x1J5kHh.jpg \n-------- ------- \n#{totalSize} 2 files\n"
|
||||
|
||||
it "should return false with small output", (done)->
|
||||
@ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
|
||||
isTooLarge.should.equal false
|
||||
done()
|
||||
@process.stdout.emit "data", @output("109042")
|
||||
@process.emit "close"
|
||||
@zipfile.emit "entry", {uncompressedSize: 109042}
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should return true with large bytes", (done)->
|
||||
@ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
|
||||
isTooLarge.should.equal true
|
||||
done()
|
||||
@process.stdout.emit "data", @output("1090000000000000042")
|
||||
@process.emit "close"
|
||||
@zipfile.emit "entry", {uncompressedSize: 1090000000000000042}
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should return error on no data", (done)->
|
||||
@ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
|
||||
expect(error).to.exist
|
||||
done()
|
||||
@process.stdout.emit "data", ""
|
||||
@process.emit "close"
|
||||
@zipfile.emit "entry", {}
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should return error if it didn't get a number", (done)->
|
||||
@ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
|
||||
expect(error).to.exist
|
||||
done()
|
||||
@process.stdout.emit "data", @output("total_size_string")
|
||||
@process.emit "close"
|
||||
@zipfile.emit "entry", {uncompressedSize:"random-error"}
|
||||
@zipfile.emit "end"
|
||||
|
||||
it "should return error if the is only a bit of data", (done)->
|
||||
it "should return error if there is no data", (done)->
|
||||
@ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
|
||||
expect(error).to.exist
|
||||
done()
|
||||
@process.stdout.emit "data", " Length Date Time Name \n--------"
|
||||
@process.emit "close"
|
||||
@zipfile.emit "end"
|
||||
|
||||
describe "findTopLevelDirectory", ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -12,9 +12,9 @@ ObjectId = require("mongojs").ObjectId
|
||||
|
||||
describe "UserInfoController", ->
|
||||
beforeEach ->
|
||||
@UserDeleter =
|
||||
@UserDeleter =
|
||||
deleteUser: sinon.stub().callsArgWith(1)
|
||||
@UserUpdater =
|
||||
@UserUpdater =
|
||||
updatePersonalInfo: sinon.stub()
|
||||
@sanitizer = escape:(v)->v
|
||||
sinon.spy @sanitizer, "escape"
|
||||
@@ -50,23 +50,47 @@ describe "UserInfoController", ->
|
||||
.should.equal true
|
||||
|
||||
describe "getPersonalInfo", ->
|
||||
beforeEach ->
|
||||
@user_id = ObjectId().toString()
|
||||
@user =
|
||||
_id: ObjectId(@user_id)
|
||||
@req.params = user_id: @user_id
|
||||
|
||||
describe "when the user exists", ->
|
||||
describe "when the user exists with sharelatex id", ->
|
||||
beforeEach ->
|
||||
@user_id = ObjectId().toString()
|
||||
@user =
|
||||
_id: ObjectId(@user_id)
|
||||
@req.params = user_id: @user_id
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user)
|
||||
@UserInfoController.sendFormattedPersonalInfo = sinon.stub()
|
||||
@UserInfoController.getPersonalInfo(@req, @res, @next)
|
||||
|
||||
it "should look up the user in the database", ->
|
||||
@UserGetter.getUser
|
||||
.calledWith(@user_id, { _id: true, first_name: true, last_name: true, email: true })
|
||||
.calledWith(
|
||||
{ _id: ObjectId(@user_id) },
|
||||
{ _id: true, first_name: true, last_name: true, email: true }
|
||||
).should.equal true
|
||||
|
||||
it "should send the formatted details back to the client", ->
|
||||
@UserInfoController.sendFormattedPersonalInfo
|
||||
.calledWith(@user, @res, @next)
|
||||
.should.equal true
|
||||
|
||||
|
||||
describe "when the user exists with overleaf id", ->
|
||||
beforeEach ->
|
||||
@user_id = 12345
|
||||
@user =
|
||||
_id: ObjectId()
|
||||
overleaf:
|
||||
id: @user_id
|
||||
@req.params = user_id: @user_id.toString()
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user)
|
||||
@UserInfoController.sendFormattedPersonalInfo = sinon.stub()
|
||||
@UserInfoController.getPersonalInfo(@req, @res, @next)
|
||||
|
||||
it "should look up the user in the database", ->
|
||||
@UserGetter.getUser
|
||||
.calledWith(
|
||||
{ "overleaf.id": @user_id },
|
||||
{ _id: true, first_name: true, last_name: true, email: true }
|
||||
).should.equal true
|
||||
|
||||
it "should send the formatted details back to the client", ->
|
||||
@UserInfoController.sendFormattedPersonalInfo
|
||||
.calledWith(@user, @res, @next)
|
||||
@@ -74,13 +98,24 @@ describe "UserInfoController", ->
|
||||
|
||||
describe "when the user does not exist", ->
|
||||
beforeEach ->
|
||||
@user_id = ObjectId().toString()
|
||||
@req.params = user_id: @user_id
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, null)
|
||||
@UserInfoController.sendFormattedPersonalInfo = sinon.stub()
|
||||
@UserInfoController.getPersonalInfo(@req, @res, @next)
|
||||
|
||||
it "should return 404 to the client", ->
|
||||
@res.statusCode.should.equal 404
|
||||
|
||||
describe "when the user id is invalid", ->
|
||||
beforeEach ->
|
||||
@user_id = "invalid"
|
||||
@req.params = user_id: @user_id
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, null)
|
||||
@UserInfoController.getPersonalInfo(@req, @res, @next)
|
||||
|
||||
it "should return 400 to the client", ->
|
||||
@res.statusCode.should.equal 400
|
||||
|
||||
describe "sendFormattedPersonalInfo", ->
|
||||
beforeEach ->
|
||||
@user =
|
||||
|
||||
@@ -4,6 +4,9 @@ User = require "./helpers/User"
|
||||
request = require "./helpers/request"
|
||||
settings = require "settings-sharelatex"
|
||||
|
||||
MockDocstoreApi = require './helpers/MockDocstoreApi'
|
||||
MockDocUpdaterApi = require './helpers/MockDocUpdaterApi'
|
||||
|
||||
try_read_access = (user, project_id, test, callback) ->
|
||||
async.series [
|
||||
(cb) ->
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
express = require("express")
|
||||
app = express()
|
||||
|
||||
module.exports = MockDocUpdaterApi =
|
||||
run: () ->
|
||||
app.post "/project/:project_id/flush", (req, res, next) =>
|
||||
res.sendStatus 200
|
||||
|
||||
app.listen 3003, (error) ->
|
||||
throw error if error?
|
||||
.on "error", (error) ->
|
||||
console.error "error starting MockDocUpdaterApi:", error.message
|
||||
process.exit(1)
|
||||
|
||||
MockDocUpdaterApi.run()
|
||||
@@ -0,0 +1,33 @@
|
||||
express = require("express")
|
||||
bodyParser = require "body-parser"
|
||||
app = express()
|
||||
|
||||
module.exports = MockDocStoreApi =
|
||||
docs: {}
|
||||
|
||||
run: () ->
|
||||
app.post "/project/:project_id/doc/:doc_id", bodyParser.json(), (req, res, next) =>
|
||||
{project_id, doc_id} = req.params
|
||||
{lines, version, ranges} = req.body
|
||||
@docs[project_id] ?= {}
|
||||
@docs[project_id][doc_id] = {lines, version, ranges}
|
||||
@docs[project_id][doc_id].rev ?= 0
|
||||
@docs[project_id][doc_id].rev += 1
|
||||
res.json {
|
||||
modified: true
|
||||
rev: @docs[project_id][doc_id].rev
|
||||
}
|
||||
|
||||
app.get "/project/:project_id/doc", (req, res, next) =>
|
||||
docs = (doc for doc_id, doc of @docs[req.params.project_id])
|
||||
res.send JSON.stringify docs
|
||||
|
||||
app.listen 3016, (error) ->
|
||||
throw error if error?
|
||||
.on "error", (error) ->
|
||||
console.error "error starting MockDocStoreApi:", error.message
|
||||
process.exit(1)
|
||||
|
||||
|
||||
MockDocStoreApi.run()
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# If you're running on OS X, you probably need to manually
|
||||
# 'rm -r node_modules/bcrypt; npm install bcrypt' inside
|
||||
# the docker container, before it will start.
|
||||
# npm rebuild bcrypt
|
||||
|
||||
echo ">> Starting server..."
|
||||
|
||||
grunt --no-color forever:app:start
|
||||
|
||||
echo ">> Server started"
|
||||
|
||||
sleep 5
|
||||
|
||||
echo ">> Running acceptance tests..."
|
||||
grunt --no-color mochaTest:acceptance
|
||||
_test_exit_code=$?
|
||||
|
||||
echo ">> Killing server"
|
||||
|
||||
grunt --no-color forever:app:stop
|
||||
|
||||
echo ">> Done"
|
||||
|
||||
exit $_test_exit_code
|
||||
Reference in New Issue
Block a user