Merge pull request #1303 from sharelatex/project-import-refactor

Project import refactor

GitOrigin-RevId: b4edc35cd4a14db0f4e6746d3da28cd94c351117
This commit is contained in:
Alasdair Smith
2018-12-20 12:06:15 +00:00
committed by sharelatex
parent 0c1e754736
commit 7112e65dc5
15 changed files with 538 additions and 84 deletions
@@ -141,8 +141,11 @@ module.exports = MockV1Api =
.on "error", (error) ->
console.error "error starting MockV1Api:", error.message
process.exit(1)
app.get '/api/v1/sharelatex/docs/:token/info', (req, res, next) =>
res.json { allow: true, exported: false }
app.get '/api/v1/sharelatex/docs/:token/is_published', (req, res, next) =>
res.json { allow: true }
app.get '/api/v1/sharelatex/users/:user_id/docs/:token/info', (req, res, next) =>
res.json { exported: false }
MockV1Api.run()
@@ -29,12 +29,17 @@ describe "TokenAccessController", ->
'../Project/ProjectController': @ProjectController = {}
'../Authentication/AuthenticationController': @AuthenticationController = {}
'./TokenAccessHandler': @TokenAccessHandler = {
getV1DocInfo: sinon.stub().yields(null, {
getV1DocPublishedInfo: sinon.stub().yields(null, {
allow: true
})
getV1DocInfo: sinon.stub().yields(null, {
exists: true
exported: false
})
}
'../../infrastructure/Features': @Features = {
hasFeature: sinon.stub().returns(false)
}
'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()}
'settings-sharelatex': {
overleaf:
@@ -250,6 +255,7 @@ describe "TokenAccessController", ->
@req.url = '/123abc'
@res = new MockResponse()
@res.redirect = sinon.stub()
@res.render = sinon.stub()
@next = sinon.stub()
@req.params['read_and_write_token'] = '123abc'
@TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
@@ -257,8 +263,11 @@ describe "TokenAccessController", ->
describe 'when project was not exported from v1', ->
beforeEach ->
@TokenAccessHandler.checkV1ProjectExported = sinon.stub()
.callsArgWith(1, null, false)
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
allow: true
exists: true
exported: false
})
@TokenAccessController.readAndWriteToken @req, @res, @next
it 'should redirect to v1', (done) ->
@@ -269,14 +278,100 @@ describe "TokenAccessController", ->
)).to.equal true
done()
describe 'when project was not exported from v1 but forcing import to v2', ->
beforeEach ->
@Features.hasFeature.returns(true)
describe 'with project name', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
allow: true
exists: true
exported: false
has_owner: true
name: 'A title'
})
@TokenAccessController.readAndWriteToken @req, @res, @next
it 'should render v2-import page with name', (done) ->
expect(@res.render.calledWith(
'project/v2-import',
{
projectId: '123abc'
name: 'A title'
hasOwner: true
}
)).to.equal true
done()
describe 'with project owner', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
allow: true
exists: true
exported: false
has_owner: true
name: 'A title'
})
@TokenAccessController.readAndWriteToken @req, @res, @next
it 'should render v2-import page', (done) ->
expect(@res.render.calledWith(
'project/v2-import',
{
projectId: '123abc',
hasOwner: true
name: 'A title'
}
)).to.equal true
done()
describe 'without project owner', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
allow: true
exists: true
exported: false
has_owner: false
name: 'A title'
})
@TokenAccessController.readAndWriteToken @req, @res, @next
it 'should render v2-import page', (done) ->
expect(@res.render.calledWith(
'project/v2-import',
{
projectId: '123abc',
hasOwner: false
name: 'A title'
}
)).to.equal true
done()
describe 'with anonymous user', ->
beforeEach ->
@AuthenticationController.getLoggedInUserId = sinon.stub().returns(null)
@TokenAccessController.readAndWriteToken @req, @res, @next
it 'should render anonymous import status page', (done) ->
expect(@res.render.callCount).to.equal 1
expect(@res.render.calledWith(
'project/v2-import',
{ loginRedirect: '/123abc' }
)).to.equal true
done()
describe 'when project was exported from v1', ->
beforeEach ->
@TokenAccessHandler.checkV1ProjectExported = sinon.stub()
.callsArgWith(1, null, false)
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
allow: true
exists: true
exported: true
})
@TokenAccessController.readAndWriteToken @req, @res, @next
it 'should call next with a not-found error', (done) ->
expect(@next.callCount).to.equal 0
expect(@next.callCount).to.equal 1
done()
describe 'when token access is off, but user has higher access anyway', ->
@@ -426,10 +521,8 @@ describe "TokenAccessController", ->
@next = sinon.stub()
@TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
.callsArgWith(1, null, @project, true)
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
@TokenAccessHandler.getV1DocPublishedInfo = sinon.stub().yields(null, {
allow: false
exists: true
exported: false
published_path: 'doc-url'
})
@TokenAccessController.readOnlyToken @req, @res, @next
@@ -565,6 +658,83 @@ describe "TokenAccessController", ->
)).to.equal true
done()
describe 'when project was not exported from v1 but forcing import to v2', ->
beforeEach ->
@Features.hasFeature.returns(true)
@req = new MockRequest()
@res = new MockResponse()
@res.render = sinon.stub()
@next = sinon.stub()
@req.params['read_only_token'] = 'abcd'
@TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
.callsArgWith(1, null, null, false)
describe 'with project name', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
allow: true
exists: true
exported: false
has_owner: true
name: 'A title'
})
@TokenAccessController.readOnlyToken @req, @res, @next
it 'should render v2-import page with name', (done) ->
expect(@res.render.calledWith(
'project/v2-import',
{
projectId: 'abcd'
name: 'A title'
hasOwner: true
}
)).to.equal true
done()
describe 'with project owner', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
allow: true
exists: true
exported: false
has_owner: true
name: 'A title'
})
@TokenAccessController.readOnlyToken @req, @res, @next
it 'should render v2-import page', (done) ->
expect(@res.render.calledWith(
'project/v2-import',
{
projectId: 'abcd',
hasOwner: true
name: 'A title'
}
)).to.equal true
done()
describe 'without project owner', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
allow: true
exists: true
exported: false
has_owner: false
name: 'A title'
})
@TokenAccessController.readOnlyToken @req, @res, @next
it 'should render v2-import page', (done) ->
expect(@res.render.calledWith(
'project/v2-import',
{
projectId: 'abcd',
hasOwner: false
name: 'A title'
}
)).to.equal true
done()
describe 'when project was exported from v1', ->
beforeEach ->
@req = new MockRequest()
@@ -811,46 +981,53 @@ describe "TokenAccessController", ->
@res.redirect = sinon.stub()
@next = sinon.stub()
@req.params['read_only_token'] = @readOnlyToken
@AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId.toString())
@TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
.callsArgWith(1, null, null)
@TokenAccessHandler.checkV1ProjectExported = sinon.stub()
.callsArgWith(1, null, false)
@TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
.callsArgWith(2, null)
@ProjectController.loadEditor = sinon.stub()
@TokenAccessController.readOnlyToken @req, @res, @next
it 'should try to find a project with this token', (done) ->
expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
.to.equal 1
expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
.to.equal true
done()
describe 'when project does not exist', ->
beforeEach ->
@TokenAccessController.readOnlyToken @req, @res, @next
it 'should not give the user session read-only access', (done) ->
expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
.to.equal 0
done()
it 'should try to find a project with this token', (done) ->
expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
.to.equal 1
expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
.to.equal true
done()
it 'should not pass control to loadEditor', (done) ->
expect(@ProjectController.loadEditor.callCount).to.equal 0
expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
done()
it 'should not give the user session read-only access', (done) ->
expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
.to.equal 0
done()
it 'should not add the user to the project with read-only access', (done) ->
expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
.to.equal 0
done()
it 'should not add the user to the project with read-only access', (done) ->
expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
.to.equal 0
done()
describe 'when project was exported to v2', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
allow: true
exists: true
exported: true
})
@TokenAccessController.readOnlyToken @req, @res, @next
it 'should call next with not found error', (done) ->
expect(@next.callCount).to.equal 1
expect(@next.calledWith(new Errors.NotFoundError())).to.equal true
done()
describe 'when project was not exported to v2', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
exists: true
exported: false
})
@TokenAccessController.readOnlyToken @req, @res, @next
it 'should redirect to v1', (done) ->
expect(@res.redirect.callCount).to.equal 1
expect(@res.redirect.calledWith(
@@ -859,3 +1036,44 @@ describe "TokenAccessController", ->
)).to.equal true
done()
describe 'anonymous user', ->
beforeEach ->
@AuthenticationController.getLoggedInUserId = sinon.stub().returns(null)
describe 'when project was not exported to v2', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
exists: true
exported: false
})
@TokenAccessController.readOnlyToken @req, @res, @next
it 'should redirect to v1', (done) ->
expect(@res.redirect.callCount).to.equal 1
expect(@res.redirect.calledWith(
302,
"/sign_in_to_v1?return_to=/read/#{@readOnlyToken}"
)).to.equal true
done()
describe 'force-import-to-v2 flag is on', ->
beforeEach ->
@res.render = sinon.stub()
@Features.hasFeature.returns(true)
describe 'when project was not exported to v2', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
exists: true
exported: false
})
@TokenAccessController.readOnlyToken @req, @res, @next
it 'should render anonymous import status page', (done) ->
expect(@res.render.callCount).to.equal 1
expect(@res.render.calledWith(
'project/v2-import',
{ loginRedirect: "/read/#{@readOnlyToken}" }
)).to.equal true
done()
@@ -21,6 +21,7 @@ describe "TokenAccessHandler", ->
'../../models/Project': {Project: @Project = {}}
'settings-sharelatex': @settings = {}
'../Collaborators/CollaboratorsHandler': @CollaboratorsHandler = {}
'../User/UserGetter': @UserGetter = {}
'../V1/V1Api': @V1Api = {
request: sinon.stub()
}
@@ -491,18 +492,53 @@ describe "TokenAccessHandler", ->
expect(@project.tokens.readAndWrite).to.equal 'rw'
expect(@project.tokens.readOnly).to.equal 'ro'
describe 'getV1DocInfo', ->
describe 'getDocPublishedInfo', ->
beforeEach ->
@callback = sinon.stub()
describe 'when v1 api not set', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo @token, @callback
@TokenAccessHandler.getV1DocPublishedInfo @token, @callback
it 'should not check access and return default info', ->
expect(@V1Api.request.called).to.equal false
expect(@callback.calledWith null, {
allow: true
}).to.equal true
describe 'when v1 api is set', ->
beforeEach ->
@settings.apis = { v1: 'v1' }
describe 'on V1Api.request success', ->
beforeEach ->
@V1Api.request = sinon.stub().callsArgWith(1, null, null, 'mock-data')
@TokenAccessHandler.getV1DocPublishedInfo @token, @callback
it 'should return response body', ->
expect(@V1Api.request.calledWith { url: "/api/v1/sharelatex/docs/#{@token}/is_published" }).to.equal true
expect(@callback.calledWith null, 'mock-data').to.equal true
describe 'on V1Api.request error', ->
beforeEach ->
@V1Api.request = sinon.stub().callsArgWith(1, 'error')
@TokenAccessHandler.getV1DocPublishedInfo @token, @callback
it 'should callback with error', ->
expect(@callback.calledWith 'error').to.equal true
describe 'getV1DocInfo', ->
beforeEach ->
@v2UserId = 123
@callback = sinon.stub()
describe 'when v1 api not set', ->
beforeEach ->
@TokenAccessHandler.getV1DocInfo @token, @v2UserId, @callback
it 'should not check access and return default info', ->
expect(@V1Api.request.called).to.equal false
expect(@callback.calledWith null, {
exists: true
exported: false
}).to.equal true
@@ -511,19 +547,45 @@ describe "TokenAccessHandler", ->
beforeEach ->
@settings.apis = { v1: 'v1' }
describe 'on success', ->
describe 'on UserGetter.getUser success', ->
beforeEach ->
@UserGetter.getUser = sinon.stub().yields(null, {
overleaf: { id: 1 }
})
@TokenAccessHandler.getV1DocInfo @token, @v2UserId, @callback
it 'should get user', ->
expect(@UserGetter.getUser.calledWith(@v2UserId)).to.equal true
describe 'on UserGetter.getUser error', ->
beforeEach ->
@error = new Error('failed to get user')
@UserGetter.getUser = sinon.stub().yields(@error)
@TokenAccessHandler.getV1DocInfo @token, @v2UserId, @callback
it 'should callback with error', ->
expect(@callback.calledWith @error).to.equal true
describe 'on V1Api.request success', ->
beforeEach ->
@v1UserId = 1
@UserGetter.getUser = sinon.stub().yields(null, {
overleaf: { id: @v1UserId }
})
@V1Api.request = sinon.stub().callsArgWith(1, null, null, 'mock-data')
@TokenAccessHandler.getV1DocInfo @token, @callback
@TokenAccessHandler.getV1DocInfo @token, @v2UserId, @callback
it 'should return response body', ->
expect(@V1Api.request.calledWith { url: "/api/v1/sharelatex/docs/#{@token}/info" }).to.equal true
expect(@V1Api.request.calledWith { url: "/api/v1/sharelatex/users/#{@v1UserId}/docs/#{@token}/info" }).to.equal true
expect(@callback.calledWith null, 'mock-data').to.equal true
describe 'on error', ->
describe 'on V1Api.request error', ->
beforeEach ->
@UserGetter.getUser = sinon.stub().yields(null, {
overleaf: { id: 1 }
})
@V1Api.request = sinon.stub().callsArgWith(1, 'error')
@TokenAccessHandler.getV1DocInfo @token, @callback
@TokenAccessHandler.getV1DocInfo @token, @v2UserId, @callback
it 'should callback with error', ->
expect(@callback.calledWith 'error').to.equal true