Merge pull request #1303 from sharelatex/project-import-refactor
Project import refactor GitOrigin-RevId: b4edc35cd4a14db0f4e6746d3da28cd94c351117
This commit is contained in:
committed by
sharelatex
parent
0c1e754736
commit
7112e65dc5
@@ -106,6 +106,7 @@ module.exports = AuthorizationMiddlewear =
|
|||||||
return callback(null, user_id)
|
return callback(null, user_id)
|
||||||
|
|
||||||
redirectToRestricted: (req, res, next) ->
|
redirectToRestricted: (req, res, next) ->
|
||||||
|
# TODO: move this to throwing ForbiddenError
|
||||||
res.redirect "/restricted?from=#{encodeURIComponent(req.url)}"
|
res.redirect "/restricted?from=#{encodeURIComponent(req.url)}"
|
||||||
|
|
||||||
restricted : (req, res, next)->
|
restricted : (req, res, next)->
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ module.exports = ErrorController =
|
|||||||
res.render 'general/404',
|
res.render 'general/404',
|
||||||
title: "page_not_found"
|
title: "page_not_found"
|
||||||
|
|
||||||
|
forbidden: (req, res) ->
|
||||||
|
res.status(403)
|
||||||
|
res.render 'user/restricted'
|
||||||
|
|
||||||
serverError: (req, res)->
|
serverError: (req, res)->
|
||||||
res.status(500)
|
res.status(500)
|
||||||
res.render 'general/500',
|
res.render 'general/500',
|
||||||
@@ -27,6 +31,9 @@ module.exports = ErrorController =
|
|||||||
if error instanceof Errors.NotFoundError
|
if error instanceof Errors.NotFoundError
|
||||||
logger.warn {err: error, url: req.url}, "not found error"
|
logger.warn {err: error, url: req.url}, "not found error"
|
||||||
ErrorController.notFound req, res
|
ErrorController.notFound req, res
|
||||||
|
else if error instanceof Errors.ForbiddenError
|
||||||
|
logger.error err: error, "forbidden error"
|
||||||
|
ErrorController.forbidden req, res
|
||||||
else if error instanceof Errors.TooManyRequestsError
|
else if error instanceof Errors.TooManyRequestsError
|
||||||
logger.warn {err: error, url: req.url}, "too many requests error"
|
logger.warn {err: error, url: req.url}, "too many requests error"
|
||||||
res.sendStatus(429)
|
res.sendStatus(429)
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ NotFoundError = (message) ->
|
|||||||
return error
|
return error
|
||||||
NotFoundError.prototype.__proto__ = Error.prototype
|
NotFoundError.prototype.__proto__ = Error.prototype
|
||||||
|
|
||||||
|
ForbiddenError = (message) ->
|
||||||
|
error = new Error(message)
|
||||||
|
error.name = "ForbiddenError"
|
||||||
|
error.__proto__ = ForbiddenError.prototype
|
||||||
|
return error
|
||||||
|
ForbiddenError.prototype.__proto__ = Error.prototype
|
||||||
|
|
||||||
ServiceNotConfiguredError = (message) ->
|
ServiceNotConfiguredError = (message) ->
|
||||||
error = new Error(message)
|
error = new Error(message)
|
||||||
error.name = "ServiceNotConfiguredError"
|
error.name = "ServiceNotConfiguredError"
|
||||||
@@ -105,6 +112,7 @@ SLInV2Error.prototype.__proto__ = Error.prototype
|
|||||||
|
|
||||||
module.exports = Errors =
|
module.exports = Errors =
|
||||||
NotFoundError: NotFoundError
|
NotFoundError: NotFoundError
|
||||||
|
ForbiddenError: ForbiddenError
|
||||||
ServiceNotConfiguredError: ServiceNotConfiguredError
|
ServiceNotConfiguredError: ServiceNotConfiguredError
|
||||||
TooManyRequestsError: TooManyRequestsError
|
TooManyRequestsError: TooManyRequestsError
|
||||||
InvalidNameError: InvalidNameError
|
InvalidNameError: InvalidNameError
|
||||||
|
|||||||
@@ -153,27 +153,29 @@ module.exports = HistoryController =
|
|||||||
if !v1_id?
|
if !v1_id?
|
||||||
logger.err {project_id, version}, 'got request for zip version of non-v1 history project'
|
logger.err {project_id, version}, 'got request for zip version of non-v1 history project'
|
||||||
return res.sendStatus(402)
|
return res.sendStatus(402)
|
||||||
|
HistoryController._pipeHistoryZipToResponse v1_id, version, "#{project.name} (Version #{version})", res, next
|
||||||
|
|
||||||
url = "#{settings.apis.v1_history.url}/projects/#{v1_id}/version/#{version}/zip"
|
_pipeHistoryZipToResponse: (v1_project_id, version, name, res, next) ->
|
||||||
logger.log {project_id, v1_id, version, url}, "proxying to history api"
|
url = "#{settings.apis.v1_history.url}/projects/#{v1_project_id}/version/#{version}/zip"
|
||||||
getReq = request(
|
logger.log {v1_project_id, version, url}, "proxying to history api"
|
||||||
url: url
|
getReq = request(
|
||||||
auth:
|
url: url
|
||||||
user: settings.apis.v1_history.user
|
auth:
|
||||||
pass: settings.apis.v1_history.pass
|
user: settings.apis.v1_history.user
|
||||||
sendImmediately: true
|
pass: settings.apis.v1_history.pass
|
||||||
|
sendImmediately: true
|
||||||
|
)
|
||||||
|
getReq.on 'response', (response) ->
|
||||||
|
# pipe also proxies the headers, but we want to customize these ones
|
||||||
|
delete response.headers['content-disposition']
|
||||||
|
delete response.headers['content-type']
|
||||||
|
res.status response.statusCode
|
||||||
|
res.setContentDisposition(
|
||||||
|
'attachment',
|
||||||
|
{filename: "#{name}.zip"}
|
||||||
)
|
)
|
||||||
getReq.on 'response', (response) ->
|
res.contentType('application/zip')
|
||||||
# pipe also proxies the headers, but we want to customize these ones
|
getReq.pipe(res)
|
||||||
delete response.headers['content-disposition']
|
getReq.on "error", (err) ->
|
||||||
delete response.headers['content-type']
|
logger.error {err, v1_project_id, version}, "history API error"
|
||||||
res.status response.statusCode
|
next(error)
|
||||||
res.setContentDisposition(
|
|
||||||
'attachment',
|
|
||||||
{filename: "#{project.name} (Version #{version}).zip"}
|
|
||||||
)
|
|
||||||
res.contentType('application/zip')
|
|
||||||
getReq.pipe(res)
|
|
||||||
getReq.on "error", (err) ->
|
|
||||||
logger.error {err, project_id, v1_id, version}, "history API error"
|
|
||||||
next(error)
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
ProjectController = require "../Project/ProjectController"
|
ProjectController = require "../Project/ProjectController"
|
||||||
AuthenticationController = require '../Authentication/AuthenticationController'
|
AuthenticationController = require '../Authentication/AuthenticationController'
|
||||||
TokenAccessHandler = require './TokenAccessHandler'
|
TokenAccessHandler = require './TokenAccessHandler'
|
||||||
|
Features = require '../../infrastructure/Features'
|
||||||
Errors = require '../Errors/Errors'
|
Errors = require '../Errors/Errors'
|
||||||
logger = require 'logger-sharelatex'
|
logger = require 'logger-sharelatex'
|
||||||
settings = require 'settings-sharelatex'
|
settings = require 'settings-sharelatex'
|
||||||
@@ -37,10 +38,13 @@ module.exports = TokenAccessController =
|
|||||||
if !projectExists and settings.overleaf
|
if !projectExists and settings.overleaf
|
||||||
logger.log {token, userId},
|
logger.log {token, userId},
|
||||||
"[TokenAccess] no project found for this token"
|
"[TokenAccess] no project found for this token"
|
||||||
TokenAccessHandler.getV1DocInfo token, (err, doc_info) ->
|
TokenAccessController._handleV1Project(
|
||||||
return next err if err?
|
token,
|
||||||
return next(new Errors.NotFoundError()) if doc_info.exported
|
userId,
|
||||||
return res.redirect(302, "/sign_in_to_v1?return_to=/#{token}")
|
"/#{token}",
|
||||||
|
res,
|
||||||
|
next
|
||||||
|
)
|
||||||
else if !project?
|
else if !project?
|
||||||
logger.log {token, userId},
|
logger.log {token, userId},
|
||||||
"[TokenAccess] no token-based project found for readAndWrite token"
|
"[TokenAccess] no token-based project found for readAndWrite token"
|
||||||
@@ -79,8 +83,9 @@ module.exports = TokenAccessController =
|
|||||||
userId = AuthenticationController.getLoggedInUserId(req)
|
userId = AuthenticationController.getLoggedInUserId(req)
|
||||||
token = req.params['read_only_token']
|
token = req.params['read_only_token']
|
||||||
logger.log {userId, token}, "[TokenAccess] requesting read-only token access"
|
logger.log {userId, token}, "[TokenAccess] requesting read-only token access"
|
||||||
TokenAccessHandler.getV1DocInfo token, (err, doc_info) ->
|
TokenAccessHandler.getV1DocPublishedInfo token, (err, doc_published_info) ->
|
||||||
return res.redirect doc_info.published_path if doc_info.allow == false
|
return next err if err?
|
||||||
|
return res.redirect doc_published_info.published_path if doc_published_info.allow == false
|
||||||
|
|
||||||
TokenAccessHandler.findProjectWithReadOnlyToken token, (err, project, projectExists) ->
|
TokenAccessHandler.findProjectWithReadOnlyToken token, (err, project, projectExists) ->
|
||||||
if err?
|
if err?
|
||||||
@@ -90,8 +95,13 @@ module.exports = TokenAccessController =
|
|||||||
if !projectExists and settings.overleaf
|
if !projectExists and settings.overleaf
|
||||||
logger.log {token, userId},
|
logger.log {token, userId},
|
||||||
"[TokenAccess] no project found for this token"
|
"[TokenAccess] no project found for this token"
|
||||||
return next(new Errors.NotFoundError()) if doc_info.exported
|
TokenAccessController._handleV1Project(
|
||||||
return res.redirect(302, "/sign_in_to_v1?return_to=/read/#{token}")
|
token,
|
||||||
|
userId,
|
||||||
|
"/read/#{token}",
|
||||||
|
res,
|
||||||
|
next
|
||||||
|
)
|
||||||
else if !project?
|
else if !project?
|
||||||
logger.log {token, userId},
|
logger.log {token, userId},
|
||||||
"[TokenAccess] no project found for readOnly token"
|
"[TokenAccess] no project found for readOnly token"
|
||||||
@@ -120,3 +130,22 @@ module.exports = TokenAccessController =
|
|||||||
"[TokenAccess] error adding user to project with readAndWrite token"
|
"[TokenAccess] error adding user to project with readAndWrite token"
|
||||||
return next(err)
|
return next(err)
|
||||||
return TokenAccessController._loadEditor(project._id, req, res, next)
|
return TokenAccessController._loadEditor(project._id, req, res, next)
|
||||||
|
|
||||||
|
_handleV1Project: (token, userId, redirectPath, res, next) ->
|
||||||
|
if !userId?
|
||||||
|
if Features.hasFeature('force-import-to-v2')
|
||||||
|
return res.render('project/v2-import', { loginRedirect: redirectPath })
|
||||||
|
else
|
||||||
|
return res.redirect(302, "/sign_in_to_v1?return_to=#{redirectPath}")
|
||||||
|
else
|
||||||
|
TokenAccessHandler.getV1DocInfo token, userId, (err, doc_info) ->
|
||||||
|
return next err if err?
|
||||||
|
return next(new Errors.NotFoundError()) if doc_info.exported
|
||||||
|
if Features.hasFeature('force-import-to-v2')
|
||||||
|
return res.render('project/v2-import', {
|
||||||
|
projectId: token,
|
||||||
|
hasOwner: doc_info.has_owner,
|
||||||
|
name: doc_info.name
|
||||||
|
})
|
||||||
|
else
|
||||||
|
return res.redirect(302, "/sign_in_to_v1?return_to=#{redirectPath}")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ Project = require('../../models/Project').Project
|
|||||||
CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
|
CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
|
||||||
PublicAccessLevels = require '../Authorization/PublicAccessLevels'
|
PublicAccessLevels = require '../Authorization/PublicAccessLevels'
|
||||||
PrivilegeLevels = require '../Authorization/PrivilegeLevels'
|
PrivilegeLevels = require '../Authorization/PrivilegeLevels'
|
||||||
|
UserGetter = require '../User/UserGetter'
|
||||||
ObjectId = require("mongojs").ObjectId
|
ObjectId = require("mongojs").ObjectId
|
||||||
Settings = require('settings-sharelatex')
|
Settings = require('settings-sharelatex')
|
||||||
V1Api = require "../V1/V1Api"
|
V1Api = require "../V1/V1Api"
|
||||||
@@ -110,14 +111,31 @@ module.exports = TokenAccessHandler =
|
|||||||
if privilegeLevel != PrivilegeLevels.READ_ONLY
|
if privilegeLevel != PrivilegeLevels.READ_ONLY
|
||||||
project.tokens.readOnly = ''
|
project.tokens.readOnly = ''
|
||||||
|
|
||||||
getV1DocInfo: (token, callback=(err, info)->) ->
|
getV1DocPublishedInfo: (token, callback = (err, publishedInfo) ->) ->
|
||||||
# default to allowing access and not exported
|
# default to allowing access
|
||||||
return callback(null, {
|
return callback(null, {
|
||||||
allow: true
|
allow: true
|
||||||
|
}) unless Settings.apis?.v1?
|
||||||
|
|
||||||
|
V1Api.request { url: "/api/v1/sharelatex/docs/#{token}/is_published" }, (err, response, body) ->
|
||||||
|
return callback err if err?
|
||||||
|
callback null, body
|
||||||
|
|
||||||
|
getV1DocInfo: (token, v2UserId, callback=(err, info)->) ->
|
||||||
|
# default to not exported
|
||||||
|
return callback(null, {
|
||||||
exists: true
|
exists: true
|
||||||
exported: false
|
exported: false
|
||||||
}) unless Settings.apis?.v1?
|
}) unless Settings.apis?.v1?
|
||||||
|
|
||||||
V1Api.request { url: "/api/v1/sharelatex/docs/#{token}/info" }, (err, response, body) ->
|
UserGetter.getUser v2UserId, { overleaf: 1 }, (err, user) ->
|
||||||
return callback err if err?
|
return callback(err) if err?
|
||||||
callback null, body
|
v1UserId = user.overleaf?.id
|
||||||
|
V1Api.request { url: "/api/v1/sharelatex/users/#{v1UserId}/docs/#{token}/info" }, (err, response, body) ->
|
||||||
|
return callback err if err?
|
||||||
|
callback null, body
|
||||||
|
|
||||||
|
module.exports.READ_AND_WRITE_TOKEN_REGEX = /^(\d+)(\w+)$/
|
||||||
|
module.exports.READ_AND_WRITE_URL_REGEX = /^\/(\d+)(\w+)$/
|
||||||
|
module.exports.READ_ONLY_TOKEN_REGEX = /^([a-z]{12})$/
|
||||||
|
module.exports.READ_ONLY_URL_REGEX = /^\/read\/([a-z]{12})$/
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
request = require 'request'
|
request = require 'request'
|
||||||
settings = require 'settings-sharelatex'
|
settings = require 'settings-sharelatex'
|
||||||
|
Errors = require '../Errors/Errors'
|
||||||
|
|
||||||
# TODO: check what happens when these settings aren't defined
|
# TODO: check what happens when these settings aren't defined
|
||||||
DEFAULT_V1_PARAMS = {
|
DEFAULT_V1_PARAMS = {
|
||||||
@@ -38,6 +39,10 @@ module.exports = V1Api =
|
|||||||
return callback(error, response, body) if error?
|
return callback(error, response, body) if error?
|
||||||
if 200 <= response.statusCode < 300 or response.statusCode in (options.expectedStatusCodes or [])
|
if 200 <= response.statusCode < 300 or response.statusCode in (options.expectedStatusCodes or [])
|
||||||
callback null, response, body
|
callback null, response, body
|
||||||
|
else if response.statusCode == 403
|
||||||
|
error = new Errors.ForbiddenError("overleaf v1 returned forbidden")
|
||||||
|
error.statusCode = response.statusCode
|
||||||
|
callback error
|
||||||
else
|
else
|
||||||
error = new Error("overleaf v1 returned non-success code: #{response.statusCode} #{options.method} #{options.uri}")
|
error = new Error("overleaf v1 returned non-success code: #{response.statusCode} #{options.method} #{options.uri}")
|
||||||
error.statusCode = response.statusCode
|
error.statusCode = response.statusCode
|
||||||
|
|||||||
@@ -29,5 +29,7 @@ module.exports = Features =
|
|||||||
Settings.overleaf? and isEnabled
|
Settings.overleaf? and isEnabled
|
||||||
when 'redirect-sl'
|
when 'redirect-sl'
|
||||||
return Settings.redirectToV2?
|
return Settings.redirectToV2?
|
||||||
|
when 'force-import-to-v2'
|
||||||
|
return Settings.forceImportToV2
|
||||||
else
|
else
|
||||||
throw new Error("unknown feature: #{feature}")
|
throw new Error("unknown feature: #{feature}")
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
extends ../layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
main.content
|
||||||
|
.container
|
||||||
|
.row
|
||||||
|
.col-sm-8.col-sm-offset-2
|
||||||
|
h1.text-center Move project to Overleaf v2
|
||||||
|
img.v2-import__img(
|
||||||
|
src="/img/v1-import/v2-editor.png"
|
||||||
|
alt="The new V2 editor."
|
||||||
|
)
|
||||||
|
|
||||||
|
if loginRedirect
|
||||||
|
p.text-center.row-spaced-small
|
||||||
|
| This project has not yet been moved into the new version of
|
||||||
|
| Overleaf. You will need to log in and move it in order to
|
||||||
|
| continue working on it.
|
||||||
|
|
||||||
|
.row-spaced.text-center
|
||||||
|
a.btn.btn-primary(
|
||||||
|
href="/login?redir=" + loginRedirect
|
||||||
|
) Log In To Move Project
|
||||||
|
else if hasOwner
|
||||||
|
p.text-center.row-spaced-small
|
||||||
|
| #[strong #{name}] has not yet been moved into the new version of
|
||||||
|
| Overleaf. You will need to move it in order to continue working
|
||||||
|
| on it. It should only take a few seconds.
|
||||||
|
|
||||||
|
form(
|
||||||
|
async-form="v2Import"
|
||||||
|
name="v2ImportForm"
|
||||||
|
action="/overleaf/project/"+ projectId + "/import"
|
||||||
|
method="POST"
|
||||||
|
ng-cloak
|
||||||
|
)
|
||||||
|
input(name='_csrf', type='hidden', value=csrfToken)
|
||||||
|
form-messages(for="v2ImportForm")
|
||||||
|
input.row-spaced.btn.btn-primary.text-center.center-block(
|
||||||
|
type="submit"
|
||||||
|
value="Move Project and Continue"
|
||||||
|
ng-disabled="v2ImportForm.inflight || v2ImportForm.response.success"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
p.text-center.row-spaced.small
|
||||||
|
| #[strong #{name}] has not yet been moved into the new version of
|
||||||
|
| Overleaf. This project was created anonymously and therefore
|
||||||
|
| cannot be automatically imported. Please download a zip file of
|
||||||
|
| the project and upload that to continue editing it.
|
||||||
|
|
||||||
|
form(
|
||||||
|
async-form="v2ZipDownload"
|
||||||
|
name="v2ZipDownloadForm"
|
||||||
|
async-form-download-response="true"
|
||||||
|
method="GET"
|
||||||
|
action="/overleaf/project/" + projectId + "/download/zip"
|
||||||
|
)
|
||||||
|
form-messages(form="v2ZipDownloadForm")
|
||||||
|
input.row-spaced.btn.btn-primary.text-center.center-block(
|
||||||
|
type="submit"
|
||||||
|
value="Download project zip file"
|
||||||
|
ng-disabled="v2ZipDownloadForm.inflight || v2ZipDownloadForm.success"
|
||||||
|
)
|
||||||
|
|
||||||
|
h3 What can I do once I've moved my project?
|
||||||
|
ul
|
||||||
|
li New & Improved Editor
|
||||||
|
li Tracked Changed & Comments
|
||||||
|
li Git access (still supported)
|
||||||
|
|
||||||
|
h3 Is there anything I can't do once I've moved my project to
|
||||||
|
ul
|
||||||
|
li Assignments
|
||||||
|
li F1000 Research Workflow
|
||||||
|
li
|
||||||
|
| Files you have imported from CiteULike and Plotly will become
|
||||||
|
| ordinary files, but you can still use these services by
|
||||||
|
| downloading and uploading files
|
||||||
|
li
|
||||||
|
| Publishing to Figshare and PeerWith are not presently available,
|
||||||
|
| but improved versions will be coming back
|
||||||
@@ -55,13 +55,12 @@ define(['base', 'libs/passfield'], function(App) {
|
|||||||
|
|
||||||
// for asyncForm prevent automatic redirect to /login if
|
// for asyncForm prevent automatic redirect to /login if
|
||||||
// authentication fails, we will handle it ourselves
|
// authentication fails, we will handle it ourselves
|
||||||
return $http
|
const httpRequestFn = _httpRequestFn(element.attr('method'))
|
||||||
.post(element.attr('action'), formData, {
|
return httpRequestFn(element.attr('action'), formData, {
|
||||||
disableAutoLoginRedirect: true
|
disableAutoLoginRedirect: true
|
||||||
})
|
})
|
||||||
.then(function(httpResponse) {
|
.then(function(httpResponse) {
|
||||||
let config, headers, status
|
const { data, headers } = httpResponse
|
||||||
;({ data, status, headers, config } = httpResponse)
|
|
||||||
scope[attrs.name].inflight = false
|
scope[attrs.name].inflight = false
|
||||||
response.success = true
|
response.success = true
|
||||||
response.error = false
|
response.error = false
|
||||||
@@ -85,6 +84,11 @@ define(['base', 'libs/passfield'], function(App) {
|
|||||||
} else {
|
} else {
|
||||||
return ga('send', 'event', formName, 'success')
|
return ga('send', 'event', formName, 'success')
|
||||||
}
|
}
|
||||||
|
} else if (scope.$eval(attrs.asyncFormDownloadResponse)) {
|
||||||
|
const blob = new Blob([data], {
|
||||||
|
type: headers('Content-Type')
|
||||||
|
})
|
||||||
|
location.href = URL.createObjectURL(blob) // Trigger file save
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function(httpResponse) {
|
.catch(function(httpResponse) {
|
||||||
@@ -137,6 +141,14 @@ define(['base', 'libs/passfield'], function(App) {
|
|||||||
const submit = () =>
|
const submit = () =>
|
||||||
validateCaptchaIfEnabled(response => submitRequest(response))
|
validateCaptchaIfEnabled(response => submitRequest(response))
|
||||||
|
|
||||||
|
const _httpRequestFn = (method = 'post') => {
|
||||||
|
const $HTTP_FNS = {
|
||||||
|
post: $http.post,
|
||||||
|
get: $http.get
|
||||||
|
}
|
||||||
|
return $HTTP_FNS[method.toLowerCase()]
|
||||||
|
}
|
||||||
|
|
||||||
element.on('submit', function(e) {
|
element.on('submit', function(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return submit()
|
return submit()
|
||||||
|
|||||||
@@ -7,4 +7,5 @@
|
|||||||
@import "app/institution-hub.less";
|
@import "app/institution-hub.less";
|
||||||
@import "app/publisher-hub.less";
|
@import "app/publisher-hub.less";
|
||||||
@import "app/admin-hub.less";
|
@import "app/admin-hub.less";
|
||||||
|
@import "app/import.less";
|
||||||
@import "components/overbox.less";
|
@import "components/overbox.less";
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.v2-import__img {
|
||||||
|
.img-responsive;
|
||||||
|
.center-block;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
@@ -141,8 +141,11 @@ module.exports = MockV1Api =
|
|||||||
.on "error", (error) ->
|
.on "error", (error) ->
|
||||||
console.error "error starting MockV1Api:", error.message
|
console.error "error starting MockV1Api:", error.message
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
|
|
||||||
app.get '/api/v1/sharelatex/docs/:token/info', (req, res, next) =>
|
app.get '/api/v1/sharelatex/docs/:token/is_published', (req, res, next) =>
|
||||||
res.json { allow: true, exported: false }
|
res.json { allow: true }
|
||||||
|
|
||||||
|
app.get '/api/v1/sharelatex/users/:user_id/docs/:token/info', (req, res, next) =>
|
||||||
|
res.json { exported: false }
|
||||||
|
|
||||||
MockV1Api.run()
|
MockV1Api.run()
|
||||||
|
|||||||
@@ -29,12 +29,17 @@ describe "TokenAccessController", ->
|
|||||||
'../Project/ProjectController': @ProjectController = {}
|
'../Project/ProjectController': @ProjectController = {}
|
||||||
'../Authentication/AuthenticationController': @AuthenticationController = {}
|
'../Authentication/AuthenticationController': @AuthenticationController = {}
|
||||||
'./TokenAccessHandler': @TokenAccessHandler = {
|
'./TokenAccessHandler': @TokenAccessHandler = {
|
||||||
getV1DocInfo: sinon.stub().yields(null, {
|
getV1DocPublishedInfo: sinon.stub().yields(null, {
|
||||||
allow: true
|
allow: true
|
||||||
|
})
|
||||||
|
getV1DocInfo: sinon.stub().yields(null, {
|
||||||
exists: true
|
exists: true
|
||||||
exported: false
|
exported: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
'../../infrastructure/Features': @Features = {
|
||||||
|
hasFeature: sinon.stub().returns(false)
|
||||||
|
}
|
||||||
'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()}
|
'logger-sharelatex': {log: sinon.stub(), err: sinon.stub()}
|
||||||
'settings-sharelatex': {
|
'settings-sharelatex': {
|
||||||
overleaf:
|
overleaf:
|
||||||
@@ -250,6 +255,7 @@ describe "TokenAccessController", ->
|
|||||||
@req.url = '/123abc'
|
@req.url = '/123abc'
|
||||||
@res = new MockResponse()
|
@res = new MockResponse()
|
||||||
@res.redirect = sinon.stub()
|
@res.redirect = sinon.stub()
|
||||||
|
@res.render = sinon.stub()
|
||||||
@next = sinon.stub()
|
@next = sinon.stub()
|
||||||
@req.params['read_and_write_token'] = '123abc'
|
@req.params['read_and_write_token'] = '123abc'
|
||||||
@TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
|
@TokenAccessHandler.findProjectWithReadAndWriteToken = sinon.stub()
|
||||||
@@ -257,8 +263,11 @@ describe "TokenAccessController", ->
|
|||||||
|
|
||||||
describe 'when project was not exported from v1', ->
|
describe 'when project was not exported from v1', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@TokenAccessHandler.checkV1ProjectExported = sinon.stub()
|
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
|
||||||
.callsArgWith(1, null, false)
|
allow: true
|
||||||
|
exists: true
|
||||||
|
exported: false
|
||||||
|
})
|
||||||
@TokenAccessController.readAndWriteToken @req, @res, @next
|
@TokenAccessController.readAndWriteToken @req, @res, @next
|
||||||
|
|
||||||
it 'should redirect to v1', (done) ->
|
it 'should redirect to v1', (done) ->
|
||||||
@@ -269,14 +278,100 @@ describe "TokenAccessController", ->
|
|||||||
)).to.equal true
|
)).to.equal true
|
||||||
done()
|
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', ->
|
describe 'when project was exported from v1', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@TokenAccessHandler.checkV1ProjectExported = sinon.stub()
|
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
|
||||||
.callsArgWith(1, null, false)
|
allow: true
|
||||||
|
exists: true
|
||||||
|
exported: true
|
||||||
|
})
|
||||||
@TokenAccessController.readAndWriteToken @req, @res, @next
|
@TokenAccessController.readAndWriteToken @req, @res, @next
|
||||||
|
|
||||||
it 'should call next with a not-found error', (done) ->
|
it 'should call next with a not-found error', (done) ->
|
||||||
expect(@next.callCount).to.equal 0
|
expect(@next.callCount).to.equal 1
|
||||||
done()
|
done()
|
||||||
|
|
||||||
describe 'when token access is off, but user has higher access anyway', ->
|
describe 'when token access is off, but user has higher access anyway', ->
|
||||||
@@ -426,10 +521,8 @@ describe "TokenAccessController", ->
|
|||||||
@next = sinon.stub()
|
@next = sinon.stub()
|
||||||
@TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
|
@TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
|
||||||
.callsArgWith(1, null, @project, true)
|
.callsArgWith(1, null, @project, true)
|
||||||
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
|
@TokenAccessHandler.getV1DocPublishedInfo = sinon.stub().yields(null, {
|
||||||
allow: false
|
allow: false
|
||||||
exists: true
|
|
||||||
exported: false
|
|
||||||
published_path: 'doc-url'
|
published_path: 'doc-url'
|
||||||
})
|
})
|
||||||
@TokenAccessController.readOnlyToken @req, @res, @next
|
@TokenAccessController.readOnlyToken @req, @res, @next
|
||||||
@@ -565,6 +658,83 @@ describe "TokenAccessController", ->
|
|||||||
)).to.equal true
|
)).to.equal true
|
||||||
done()
|
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', ->
|
describe 'when project was exported from v1', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@req = new MockRequest()
|
@req = new MockRequest()
|
||||||
@@ -811,46 +981,53 @@ describe "TokenAccessController", ->
|
|||||||
@res.redirect = sinon.stub()
|
@res.redirect = sinon.stub()
|
||||||
@next = sinon.stub()
|
@next = sinon.stub()
|
||||||
@req.params['read_only_token'] = @readOnlyToken
|
@req.params['read_only_token'] = @readOnlyToken
|
||||||
|
@AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId.toString())
|
||||||
@TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
|
@TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
|
||||||
.callsArgWith(1, null, null)
|
|
||||||
@TokenAccessHandler.checkV1ProjectExported = sinon.stub()
|
|
||||||
.callsArgWith(1, null, false)
|
.callsArgWith(1, null, false)
|
||||||
@TokenAccessHandler.addReadOnlyUserToProject = sinon.stub()
|
@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) ->
|
describe 'when project does not exist', ->
|
||||||
expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
|
beforeEach ->
|
||||||
.to.equal 1
|
@TokenAccessController.readOnlyToken @req, @res, @next
|
||||||
expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
|
|
||||||
.to.equal true
|
|
||||||
done()
|
|
||||||
|
|
||||||
it 'should not give the user session read-only access', (done) ->
|
it 'should try to find a project with this token', (done) ->
|
||||||
expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
|
expect(@TokenAccessHandler.findProjectWithReadOnlyToken.callCount)
|
||||||
.to.equal 0
|
.to.equal 1
|
||||||
done()
|
expect(@TokenAccessHandler.findProjectWithReadOnlyToken.calledWith(@readOnlyToken))
|
||||||
|
.to.equal true
|
||||||
|
done()
|
||||||
|
|
||||||
it 'should not pass control to loadEditor', (done) ->
|
it 'should not give the user session read-only access', (done) ->
|
||||||
expect(@ProjectController.loadEditor.callCount).to.equal 0
|
expect(@TokenAccessHandler.grantSessionTokenAccess.callCount)
|
||||||
expect(@ProjectController.loadEditor.calledWith(@req, @res, @next)).to.equal false
|
.to.equal 0
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it 'should not add the user to the project with read-only access', (done) ->
|
it 'should not add the user to the project with read-only access', (done) ->
|
||||||
expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
|
expect(@TokenAccessHandler.addReadOnlyUserToProject.callCount)
|
||||||
.to.equal 0
|
.to.equal 0
|
||||||
done()
|
done()
|
||||||
|
|
||||||
describe 'when project was exported to v2', ->
|
describe 'when project was exported to v2', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
|
@TokenAccessHandler.getV1DocInfo = sinon.stub().yields(null, {
|
||||||
allow: true
|
|
||||||
exists: true
|
exists: true
|
||||||
exported: true
|
exported: true
|
||||||
})
|
})
|
||||||
@TokenAccessController.readOnlyToken @req, @res, @next
|
@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) ->
|
it 'should redirect to v1', (done) ->
|
||||||
expect(@res.redirect.callCount).to.equal 1
|
expect(@res.redirect.callCount).to.equal 1
|
||||||
expect(@res.redirect.calledWith(
|
expect(@res.redirect.calledWith(
|
||||||
@@ -859,3 +1036,44 @@ describe "TokenAccessController", ->
|
|||||||
)).to.equal true
|
)).to.equal true
|
||||||
done()
|
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 = {}}
|
'../../models/Project': {Project: @Project = {}}
|
||||||
'settings-sharelatex': @settings = {}
|
'settings-sharelatex': @settings = {}
|
||||||
'../Collaborators/CollaboratorsHandler': @CollaboratorsHandler = {}
|
'../Collaborators/CollaboratorsHandler': @CollaboratorsHandler = {}
|
||||||
|
'../User/UserGetter': @UserGetter = {}
|
||||||
'../V1/V1Api': @V1Api = {
|
'../V1/V1Api': @V1Api = {
|
||||||
request: sinon.stub()
|
request: sinon.stub()
|
||||||
}
|
}
|
||||||
@@ -491,18 +492,53 @@ describe "TokenAccessHandler", ->
|
|||||||
expect(@project.tokens.readAndWrite).to.equal 'rw'
|
expect(@project.tokens.readAndWrite).to.equal 'rw'
|
||||||
expect(@project.tokens.readOnly).to.equal 'ro'
|
expect(@project.tokens.readOnly).to.equal 'ro'
|
||||||
|
|
||||||
describe 'getV1DocInfo', ->
|
describe 'getDocPublishedInfo', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
|
|
||||||
describe 'when v1 api not set', ->
|
describe 'when v1 api not set', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@TokenAccessHandler.getV1DocInfo @token, @callback
|
@TokenAccessHandler.getV1DocPublishedInfo @token, @callback
|
||||||
|
|
||||||
it 'should not check access and return default info', ->
|
it 'should not check access and return default info', ->
|
||||||
expect(@V1Api.request.called).to.equal false
|
expect(@V1Api.request.called).to.equal false
|
||||||
expect(@callback.calledWith null, {
|
expect(@callback.calledWith null, {
|
||||||
allow: true
|
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
|
exists: true
|
||||||
exported: false
|
exported: false
|
||||||
}).to.equal true
|
}).to.equal true
|
||||||
@@ -511,19 +547,45 @@ describe "TokenAccessHandler", ->
|
|||||||
beforeEach ->
|
beforeEach ->
|
||||||
@settings.apis = { v1: 'v1' }
|
@settings.apis = { v1: 'v1' }
|
||||||
|
|
||||||
describe 'on success', ->
|
describe 'on UserGetter.getUser success', ->
|
||||||
beforeEach ->
|
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')
|
@V1Api.request = sinon.stub().callsArgWith(1, null, null, 'mock-data')
|
||||||
@TokenAccessHandler.getV1DocInfo @token, @callback
|
@TokenAccessHandler.getV1DocInfo @token, @v2UserId, @callback
|
||||||
|
|
||||||
it 'should return response body', ->
|
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
|
expect(@callback.calledWith null, 'mock-data').to.equal true
|
||||||
|
|
||||||
describe 'on error', ->
|
describe 'on V1Api.request error', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
@UserGetter.getUser = sinon.stub().yields(null, {
|
||||||
|
overleaf: { id: 1 }
|
||||||
|
})
|
||||||
@V1Api.request = sinon.stub().callsArgWith(1, 'error')
|
@V1Api.request = sinon.stub().callsArgWith(1, 'error')
|
||||||
@TokenAccessHandler.getV1DocInfo @token, @callback
|
@TokenAccessHandler.getV1DocInfo @token, @v2UserId, @callback
|
||||||
|
|
||||||
it 'should callback with error', ->
|
it 'should callback with error', ->
|
||||||
expect(@callback.calledWith 'error').to.equal true
|
expect(@callback.calledWith 'error').to.equal true
|
||||||
|
|||||||
Reference in New Issue
Block a user