mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 06:03:45 +01:00
Merge pull request #31663 from overleaf/ar-convert-exportshandler-to-fetch-utils
[web] convert ExportsHandler to fetch utils GitOrigin-RevId: 96e74a1254e456b4ac8a6d3a2bd6330a806c8693
This commit is contained in:
@@ -1,128 +1,109 @@
|
||||
/* eslint-disable
|
||||
max-len,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import ExportsHandler from './ExportsHandler.mjs'
|
||||
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
import SessionManager from '../Authentication/SessionManager.mjs'
|
||||
import logger from '@overleaf/logger'
|
||||
import OError from '@overleaf/o-error'
|
||||
|
||||
async function exportProject(req, res, next) {
|
||||
const { project_id: projectId, brand_variation_id: brandVariationId } =
|
||||
req.params
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const exportParams = {
|
||||
project_id: projectId,
|
||||
brand_variation_id: brandVariationId,
|
||||
user_id: userId,
|
||||
}
|
||||
|
||||
if (req.body) {
|
||||
if (req.body.firstName) {
|
||||
exportParams.first_name = req.body.firstName.trim()
|
||||
}
|
||||
if (req.body.lastName) {
|
||||
exportParams.last_name = req.body.lastName.trim()
|
||||
}
|
||||
// additional parameters for gallery exports
|
||||
if (req.body.title) {
|
||||
exportParams.title = req.body.title.trim()
|
||||
}
|
||||
if (req.body.description) {
|
||||
exportParams.description = req.body.description.trim()
|
||||
}
|
||||
if (req.body.author) {
|
||||
exportParams.author = req.body.author.trim()
|
||||
}
|
||||
if (req.body.license) {
|
||||
exportParams.license = req.body.license.trim()
|
||||
}
|
||||
if (req.body.showSource != null) {
|
||||
exportParams.show_source = req.body.showSource
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const exportData = await ExportsHandler.exportProject(exportParams)
|
||||
logger.debug(
|
||||
{
|
||||
userId,
|
||||
projectId,
|
||||
brandVariationId,
|
||||
exportV1Id: exportData.v1_id,
|
||||
},
|
||||
'exported project'
|
||||
)
|
||||
return res.json({
|
||||
export_v1_id: exportData.v1_id,
|
||||
message: exportData.message,
|
||||
})
|
||||
} catch (err) {
|
||||
const info = OError.getFullInfo(err)
|
||||
if (info?.forwardResponse) {
|
||||
logger.debug(
|
||||
{ responseError: info.forwardResponse },
|
||||
'forwarding response'
|
||||
)
|
||||
const statusCode = info.forwardResponse.status || 500
|
||||
return res.status(statusCode).json(info.forwardResponse)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function exportStatus(req, res) {
|
||||
const { export_id: exportId } = req.params
|
||||
let exportJson
|
||||
try {
|
||||
exportJson = await ExportsHandler.fetchExport(exportId)
|
||||
} catch (err) {
|
||||
const json = {
|
||||
status_summary: 'failed',
|
||||
status_detail: String(err),
|
||||
}
|
||||
return res.json({ export_json: json })
|
||||
}
|
||||
const parsedExport = JSON.parse(exportJson)
|
||||
const json = {
|
||||
status_summary: parsedExport.status_summary,
|
||||
status_detail: parsedExport.status_detail,
|
||||
partner_submission_id: parsedExport.partner_submission_id,
|
||||
v2_user_email: parsedExport.v2_user_email,
|
||||
v2_user_first_name: parsedExport.v2_user_first_name,
|
||||
v2_user_last_name: parsedExport.v2_user_last_name,
|
||||
title: parsedExport.title,
|
||||
token: parsedExport.token,
|
||||
}
|
||||
return res.json({ export_json: json })
|
||||
}
|
||||
|
||||
async function exportDownload(req, res, next) {
|
||||
const { type, export_id: exportId } = req.params
|
||||
|
||||
SessionManager.getLoggedInUserId(req.session)
|
||||
const exportFileUrl = await ExportsHandler.fetchDownload(exportId, type)
|
||||
return res.redirect(exportFileUrl)
|
||||
}
|
||||
|
||||
export default {
|
||||
exportProject(req, res, next) {
|
||||
const { project_id: projectId, brand_variation_id: brandVariationId } =
|
||||
req.params
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const exportParams = {
|
||||
project_id: projectId,
|
||||
brand_variation_id: brandVariationId,
|
||||
user_id: userId,
|
||||
}
|
||||
|
||||
if (req.body) {
|
||||
if (req.body.firstName) {
|
||||
exportParams.first_name = req.body.firstName.trim()
|
||||
}
|
||||
if (req.body.lastName) {
|
||||
exportParams.last_name = req.body.lastName.trim()
|
||||
}
|
||||
// additional parameters for gallery exports
|
||||
if (req.body.title) {
|
||||
exportParams.title = req.body.title.trim()
|
||||
}
|
||||
if (req.body.description) {
|
||||
exportParams.description = req.body.description.trim()
|
||||
}
|
||||
if (req.body.author) {
|
||||
exportParams.author = req.body.author.trim()
|
||||
}
|
||||
if (req.body.license) {
|
||||
exportParams.license = req.body.license.trim()
|
||||
}
|
||||
if (req.body.showSource != null) {
|
||||
exportParams.show_source = req.body.showSource
|
||||
}
|
||||
}
|
||||
|
||||
return ExportsHandler.exportProject(
|
||||
exportParams,
|
||||
function (err, exportData) {
|
||||
if (err != null) {
|
||||
if (err.forwardResponse != null) {
|
||||
logger.debug(
|
||||
{ responseError: err.forwardResponse },
|
||||
'forwarding response'
|
||||
)
|
||||
const statusCode = err.forwardResponse.status || 500
|
||||
return res.status(statusCode).json(err.forwardResponse)
|
||||
} else {
|
||||
return next(err)
|
||||
}
|
||||
}
|
||||
logger.debug(
|
||||
{
|
||||
userId,
|
||||
projectId,
|
||||
brandVariationId,
|
||||
exportV1Id: exportData.v1_id,
|
||||
},
|
||||
'exported project'
|
||||
)
|
||||
return res.json({
|
||||
export_v1_id: exportData.v1_id,
|
||||
message: exportData.message,
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
exportStatus(req, res) {
|
||||
const { export_id: exportId } = req.params
|
||||
return ExportsHandler.fetchExport(exportId, function (err, exportJson) {
|
||||
let json
|
||||
if (err != null) {
|
||||
json = {
|
||||
status_summary: 'failed',
|
||||
status_detail: err.toString,
|
||||
}
|
||||
res.json({ export_json: json })
|
||||
return err
|
||||
}
|
||||
const parsedExport = JSON.parse(exportJson)
|
||||
json = {
|
||||
status_summary: parsedExport.status_summary,
|
||||
status_detail: parsedExport.status_detail,
|
||||
partner_submission_id: parsedExport.partner_submission_id,
|
||||
v2_user_email: parsedExport.v2_user_email,
|
||||
v2_user_first_name: parsedExport.v2_user_first_name,
|
||||
v2_user_last_name: parsedExport.v2_user_last_name,
|
||||
title: parsedExport.title,
|
||||
token: parsedExport.token,
|
||||
}
|
||||
return res.json({ export_json: json })
|
||||
})
|
||||
},
|
||||
|
||||
exportDownload(req, res, next) {
|
||||
const { type, export_id: exportId } = req.params
|
||||
|
||||
SessionManager.getLoggedInUserId(req.session)
|
||||
return ExportsHandler.fetchDownload(
|
||||
exportId,
|
||||
type,
|
||||
function (err, exportFileUrl) {
|
||||
if (err != null) {
|
||||
return next(err)
|
||||
}
|
||||
|
||||
return res.redirect(exportFileUrl)
|
||||
}
|
||||
)
|
||||
},
|
||||
exportDownload: expressify(exportDownload),
|
||||
exportProject: expressify(exportProject),
|
||||
exportStatus: expressify(exportStatus),
|
||||
}
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
/* eslint-disable
|
||||
n/handle-callback-err,
|
||||
max-len,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import OError from '@overleaf/o-error'
|
||||
import ProjectGetter from '../Project/ProjectGetter.mjs'
|
||||
import ProjectHistoryHandler from '../Project/ProjectHistoryHandler.mjs'
|
||||
@@ -19,39 +6,25 @@ import ProjectRootDocManager from '../Project/ProjectRootDocManager.mjs'
|
||||
import UserGetter from '../User/UserGetter.mjs'
|
||||
import logger from '@overleaf/logger'
|
||||
import settings from '@overleaf/settings'
|
||||
import async from 'async'
|
||||
import Request from 'request'
|
||||
import {
|
||||
fetchString,
|
||||
fetchJson,
|
||||
RequestFailedError,
|
||||
} from '@overleaf/fetch-utils'
|
||||
let ExportsHandler
|
||||
const request = Request.defaults()
|
||||
|
||||
export default ExportsHandler = {
|
||||
exportProject(exportParams, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return ExportsHandler._buildExport(
|
||||
exportParams,
|
||||
function (err, exportData) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
return ExportsHandler._requestExport(exportData, function (err, body) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
exportData.v1_id = body.exportId
|
||||
exportData.message = body.message
|
||||
// TODO: possibly store the export data in Mongo
|
||||
return callback(null, exportData)
|
||||
})
|
||||
}
|
||||
)
|
||||
async exportProject(exportParams) {
|
||||
const exportData = await ExportsHandler._buildExport(exportParams)
|
||||
const body = await ExportsHandler._requestExport(exportData)
|
||||
|
||||
exportData.v1_id = body.exportId
|
||||
exportData.message = body.message
|
||||
// TODO: possibly store the export data in Mongo
|
||||
return exportData
|
||||
},
|
||||
|
||||
_buildExport(exportParams, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
async _buildExport(exportParams) {
|
||||
const {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
@@ -62,221 +35,179 @@ export default ExportsHandler = {
|
||||
license,
|
||||
show_source: showSource,
|
||||
} = exportParams
|
||||
const jobs = {
|
||||
project(cb) {
|
||||
return ProjectGetter.getProject(projectId, cb)
|
||||
},
|
||||
rootDoc: [
|
||||
'project',
|
||||
(results, cb) =>
|
||||
ProjectRootDocManager.ensureRootDocumentIsValid(
|
||||
projectId,
|
||||
function (error) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return ProjectLocator.findRootDoc(
|
||||
{ project: results.project, project_id: projectId },
|
||||
cb
|
||||
)
|
||||
}
|
||||
),
|
||||
],
|
||||
user(cb) {
|
||||
return UserGetter.getUser(
|
||||
userId,
|
||||
{ first_name: 1, last_name: 1, email: 1, overleaf: 1 },
|
||||
cb
|
||||
)
|
||||
},
|
||||
historyVersion(cb) {
|
||||
return ProjectHistoryHandler.ensureHistoryExistsForProject(
|
||||
projectId,
|
||||
function (error) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return ExportsHandler._requestVersion(projectId, cb)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
let project, rootDoc, user, historyVersion
|
||||
|
||||
try {
|
||||
project = await ProjectGetter.promises.getProject(projectId)
|
||||
|
||||
await ProjectRootDocManager.promises.ensureRootDocumentIsValid(projectId)
|
||||
|
||||
rootDoc = await ProjectLocator.promises.findRootDoc({
|
||||
project,
|
||||
project_id: projectId,
|
||||
})
|
||||
|
||||
user = await UserGetter.promises.getUser(userId, {
|
||||
first_name: 1,
|
||||
last_name: 1,
|
||||
email: 1,
|
||||
overleaf: 1,
|
||||
})
|
||||
await ProjectHistoryHandler.promises.ensureHistoryExistsForProject(
|
||||
projectId
|
||||
)
|
||||
historyVersion = await ExportsHandler._requestVersion(projectId)
|
||||
} catch (err) {
|
||||
throw OError.tag(err, 'error building project export', {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
brand_variation_id: brandVariationId,
|
||||
})
|
||||
}
|
||||
|
||||
return async.auto(jobs, function (err, results) {
|
||||
if (err != null) {
|
||||
OError.tag(err, 'error building project export', {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
brand_variation_id: brandVariationId,
|
||||
})
|
||||
return callback(err)
|
||||
}
|
||||
if (!rootDoc || !rootDoc.path) {
|
||||
throw new OError('cannot export project without root doc', {
|
||||
project_id: projectId,
|
||||
})
|
||||
}
|
||||
|
||||
const { project, rootDoc, user, historyVersion } = results
|
||||
if (!rootDoc || rootDoc[1] == null) {
|
||||
err = new OError('cannot export project without root doc', {
|
||||
project_id: projectId,
|
||||
})
|
||||
return callback(err)
|
||||
}
|
||||
if (exportParams.first_name && exportParams.last_name) {
|
||||
user.first_name = exportParams.first_name
|
||||
user.last_name = exportParams.last_name
|
||||
}
|
||||
|
||||
if (exportParams.first_name && exportParams.last_name) {
|
||||
user.first_name = exportParams.first_name
|
||||
user.last_name = exportParams.last_name
|
||||
}
|
||||
return {
|
||||
project: {
|
||||
id: projectId,
|
||||
rootDocPath: rootDoc.path ? rootDoc.path.fileSystem : undefined,
|
||||
historyId: project.overleaf?.history?.id,
|
||||
historyVersion,
|
||||
v1ProjectId: project.overleaf != null ? project.overleaf.id : undefined,
|
||||
metadata: {
|
||||
compiler: project.compiler,
|
||||
imageName: project.imageName,
|
||||
title,
|
||||
description,
|
||||
author,
|
||||
license,
|
||||
showSource,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
id: userId,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
email: user.email,
|
||||
orcidId: null, // until v2 gets ORCID
|
||||
v1UserId: user.overleaf != null ? user.overleaf.id : undefined,
|
||||
},
|
||||
destination: {
|
||||
brandVariationId,
|
||||
},
|
||||
options: {
|
||||
callbackUrl: null,
|
||||
}, // for now, until we want v1 to call us back
|
||||
}
|
||||
},
|
||||
|
||||
const exportData = {
|
||||
project: {
|
||||
id: projectId,
|
||||
rootDocPath: rootDoc[1] != null ? rootDoc[1].fileSystem : undefined,
|
||||
historyId: project.overleaf?.history?.id,
|
||||
historyVersion,
|
||||
v1ProjectId:
|
||||
project.overleaf != null ? project.overleaf.id : undefined,
|
||||
metadata: {
|
||||
compiler: project.compiler,
|
||||
imageName: project.imageName,
|
||||
title,
|
||||
description,
|
||||
author,
|
||||
license,
|
||||
showSource,
|
||||
async _requestExport(exportData) {
|
||||
try {
|
||||
return await fetchJson(
|
||||
new URL('/api/v1/overleaf/exports', settings.apis.v1.url),
|
||||
{
|
||||
basicAuth: {
|
||||
user: settings.apis.v1.user,
|
||||
password: settings.apis.v1.pass,
|
||||
},
|
||||
json: exportData,
|
||||
signal: AbortSignal.timeout(settings.apis.v1.timeout),
|
||||
method: 'POST',
|
||||
}
|
||||
)
|
||||
} catch (err) {
|
||||
if (err instanceof RequestFailedError) {
|
||||
logger.warn(
|
||||
{ export: exportData },
|
||||
`v1 export returned failure; forwarding: ${err.body}`
|
||||
)
|
||||
// pass the v1 error along for the publish modal to handle
|
||||
throw OError.tag(err, 'v1 export returned failure', {
|
||||
forwardResponse: err.body,
|
||||
})
|
||||
}
|
||||
throw OError.tag(err, 'error making request to v1 export', {
|
||||
export: exportData,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async _requestVersion(projectId) {
|
||||
const url = new URL(settings.apis.project_history.url)
|
||||
url.pathname = `/project/${projectId}/version`
|
||||
try {
|
||||
const body = await fetchJson(url)
|
||||
return body.version
|
||||
} catch (err) {
|
||||
if (err instanceof RequestFailedError) {
|
||||
throw new OError(
|
||||
'project history version returned a failure status code',
|
||||
{ project_id: projectId, statusCode: err.response.status }
|
||||
)
|
||||
}
|
||||
throw OError.tag(err, 'error making request to project history', {
|
||||
project_id: projectId,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async fetchExport(exportId) {
|
||||
const url = new URL(settings.apis.v1.url)
|
||||
url.pathname = `/api/v1/overleaf/exports/${exportId}`
|
||||
|
||||
try {
|
||||
return await fetchString(url, {
|
||||
basicAuth: {
|
||||
user: settings.apis.v1.user,
|
||||
password: settings.apis.v1.pass,
|
||||
},
|
||||
user: {
|
||||
id: userId,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
email: user.email,
|
||||
orcidId: null, // until v2 gets ORCID
|
||||
v1UserId: user.overleaf != null ? user.overleaf.id : undefined,
|
||||
signal: AbortSignal.timeout(settings.apis.v1.timeout),
|
||||
})
|
||||
} catch (err) {
|
||||
if (err instanceof RequestFailedError) {
|
||||
throw new OError('v1 export returned a failure status code', {
|
||||
export: exportId,
|
||||
statusCode: err.response.status,
|
||||
})
|
||||
}
|
||||
throw OError.tag(err, 'error making request to v1 export', {
|
||||
export: exportId,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async fetchDownload(exportId, type) {
|
||||
const url = new URL(settings.apis.v1.url)
|
||||
url.pathname = `/api/v1/overleaf/exports/${exportId}/${type}_url`
|
||||
|
||||
try {
|
||||
return await fetchString(url, {
|
||||
basicAuth: {
|
||||
user: settings.apis.v1.user,
|
||||
password: settings.apis.v1.pass,
|
||||
},
|
||||
destination: {
|
||||
brandVariationId,
|
||||
},
|
||||
options: {
|
||||
callbackUrl: null,
|
||||
}, // for now, until we want v1 to call us back
|
||||
signal: AbortSignal.timeout(settings.apis.v1.timeout),
|
||||
})
|
||||
} catch (err) {
|
||||
if (err instanceof RequestFailedError) {
|
||||
throw new OError('v1 export returned a failure status code', {
|
||||
export: exportId,
|
||||
statusCode: err.response.status,
|
||||
})
|
||||
}
|
||||
return callback(null, exportData)
|
||||
})
|
||||
},
|
||||
|
||||
_requestExport(exportData, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
throw OError.tag(err, 'error making request to v1 export', {
|
||||
export: exportId,
|
||||
})
|
||||
}
|
||||
return request.post(
|
||||
{
|
||||
url: `${settings.apis.v1.url}/api/v1/overleaf/exports`,
|
||||
auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
|
||||
json: exportData,
|
||||
timeout: settings.apis.v1.timeout,
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err != null) {
|
||||
OError.tag(err, 'error making request to v1 export', {
|
||||
export: exportData,
|
||||
})
|
||||
return callback(err)
|
||||
} else if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
return callback(null, body)
|
||||
} else {
|
||||
logger.warn(
|
||||
{ export: exportData },
|
||||
`v1 export returned failure; forwarding: ${body}`
|
||||
)
|
||||
// pass the v1 error along for the publish modal to handle
|
||||
const err = { forwardResponse: body }
|
||||
return callback(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
_requestVersion(projectId, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.get(
|
||||
{
|
||||
url: `${settings.apis.project_history.url}/project/${projectId}/version`,
|
||||
json: true,
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err != null) {
|
||||
OError.tag(err, 'error making request to project history', {
|
||||
project_id: projectId,
|
||||
})
|
||||
return callback(err)
|
||||
} else if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
return callback(null, body.version)
|
||||
} else {
|
||||
err = new OError(
|
||||
'project history version returned a failure status code',
|
||||
{ project_id: projectId, statusCode: res.statusCode }
|
||||
)
|
||||
return callback(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
fetchExport(exportId, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.get(
|
||||
{
|
||||
url: `${settings.apis.v1.url}/api/v1/overleaf/exports/${exportId}`,
|
||||
auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
|
||||
timeout: settings.apis.v1.timeout,
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err != null) {
|
||||
OError.tag(err, 'error making request to v1 export', {
|
||||
export: exportId,
|
||||
})
|
||||
return callback(err)
|
||||
} else if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
return callback(null, body)
|
||||
} else {
|
||||
err = new OError('v1 export returned a failure status code', {
|
||||
export: exportId,
|
||||
statusCode: res.statusCode,
|
||||
})
|
||||
return callback(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
fetchDownload(exportId, type, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.get(
|
||||
{
|
||||
url: `${settings.apis.v1.url}/api/v1/overleaf/exports/${exportId}/${type}_url`,
|
||||
auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
|
||||
timeout: settings.apis.v1.timeout,
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err != null) {
|
||||
OError.tag(err, 'error making request to v1 export', {
|
||||
export: exportId,
|
||||
})
|
||||
return callback(err)
|
||||
} else if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
return callback(null, body)
|
||||
} else {
|
||||
err = new OError('v1 export returned a failure status code', {
|
||||
export: exportId,
|
||||
statusCode: res.statusCode,
|
||||
})
|
||||
return callback(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect, vi } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import OError from '@overleaf/o-error'
|
||||
const modulePath = new URL(
|
||||
'../../../../app/src/Features/Exports/ExportsController.mjs',
|
||||
import.meta.url
|
||||
@@ -66,56 +67,54 @@ describe('ExportsController', function () {
|
||||
|
||||
describe('without gallery fields', function () {
|
||||
it('should ask the handler to perform the export', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.handler.exportProject = sinon
|
||||
.stub()
|
||||
.yields(null, { iAmAnExport: true, v1_id: 897 })
|
||||
const expected = {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
brand_variation_id: brandVariationId,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
}
|
||||
return ctx.controller.exportProject(ctx.req, {
|
||||
json: body => {
|
||||
expect(ctx.handler.exportProject.args[0][0]).to.deep.equal(expected)
|
||||
expect(body).to.deep.equal({
|
||||
export_v1_id: 897,
|
||||
message: undefined,
|
||||
})
|
||||
return resolve()
|
||||
},
|
||||
})
|
||||
ctx.handler.exportProject = sinon
|
||||
.stub()
|
||||
.resolves({ iAmAnExport: true, v1_id: 897 })
|
||||
const expected = {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
brand_variation_id: brandVariationId,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
}
|
||||
const res = {
|
||||
json: sinon.stub(),
|
||||
}
|
||||
|
||||
await ctx.controller.exportProject(ctx.req, res)
|
||||
expect(ctx.handler.exportProject.args[0][0]).to.deep.equal(expected)
|
||||
expect(res.json.args[0][0]).to.deep.equal({
|
||||
export_v1_id: 897,
|
||||
message: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a message from v1', function () {
|
||||
it('should ask the handler to perform the export', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.handler.exportProject = sinon.stub().yields(null, {
|
||||
iAmAnExport: true,
|
||||
v1_id: 897,
|
||||
message: 'RESUBMISSION',
|
||||
})
|
||||
const expected = {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
brand_variation_id: brandVariationId,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
}
|
||||
return ctx.controller.exportProject(ctx.req, {
|
||||
json: body => {
|
||||
expect(ctx.handler.exportProject.args[0][0]).to.deep.equal(expected)
|
||||
expect(body).to.deep.equal({
|
||||
export_v1_id: 897,
|
||||
message: 'RESUBMISSION',
|
||||
})
|
||||
return resolve()
|
||||
},
|
||||
})
|
||||
ctx.handler.exportProject = sinon.stub().resolves({
|
||||
iAmAnExport: true,
|
||||
v1_id: 897,
|
||||
message: 'RESUBMISSION',
|
||||
})
|
||||
const expected = {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
brand_variation_id: brandVariationId,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
}
|
||||
|
||||
const res = {
|
||||
json: sinon.stub(),
|
||||
}
|
||||
|
||||
await ctx.controller.exportProject(ctx.req, res)
|
||||
|
||||
expect(ctx.handler.exportProject.args[0][0]).to.deep.equal(expected)
|
||||
expect(res.json.args[0][0]).to.deep.equal({
|
||||
export_v1_id: 897,
|
||||
message: 'RESUBMISSION',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -130,56 +129,52 @@ describe('ExportsController', function () {
|
||||
})
|
||||
|
||||
it('should ask the handler to perform the export', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.handler.exportProject = sinon
|
||||
.stub()
|
||||
.yields(null, { iAmAnExport: true, v1_id: 897 })
|
||||
const expected = {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
brand_variation_id: brandVariationId,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
title,
|
||||
description,
|
||||
author,
|
||||
license,
|
||||
show_source: showSource,
|
||||
}
|
||||
return ctx.controller.exportProject(ctx.req, {
|
||||
json: body => {
|
||||
expect(ctx.handler.exportProject.args[0][0]).to.deep.equal(expected)
|
||||
expect(body).to.deep.equal({
|
||||
export_v1_id: 897,
|
||||
message: undefined,
|
||||
})
|
||||
return resolve()
|
||||
},
|
||||
})
|
||||
ctx.handler.exportProject = sinon
|
||||
.stub()
|
||||
.resolves({ iAmAnExport: true, v1_id: 897 })
|
||||
const expected = {
|
||||
project_id: projectId,
|
||||
user_id: userId,
|
||||
brand_variation_id: brandVariationId,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
title,
|
||||
description,
|
||||
author,
|
||||
license,
|
||||
show_source: showSource,
|
||||
}
|
||||
|
||||
const res = {
|
||||
json: sinon.stub(),
|
||||
}
|
||||
|
||||
await ctx.controller.exportProject(ctx.req, res)
|
||||
expect(ctx.handler.exportProject.args[0][0]).to.deep.equal(expected)
|
||||
expect(res.json.args[0][0]).to.deep.equal({
|
||||
export_v1_id: 897,
|
||||
message: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with an error return from v1 to forward to the publish modal', function () {
|
||||
it('should forward the response onward', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.error_json = { status: 422, message: 'nope' }
|
||||
ctx.handler.exportProject = sinon
|
||||
.stub()
|
||||
.yields({ forwardResponse: ctx.error_json })
|
||||
ctx.controller.exportProject(ctx.req, ctx.res, ctx.next)
|
||||
expect(ctx.res.json.args[0][0]).to.deep.equal(ctx.error_json)
|
||||
expect(ctx.res.status.args[0][0]).to.equal(ctx.error_json.status)
|
||||
return resolve()
|
||||
})
|
||||
ctx.error_json = { status: 422, message: 'nope' }
|
||||
ctx.handler.exportProject = sinon.stub().rejects(
|
||||
OError.tag(new Error('original error'), 'v1 error', {
|
||||
forwardResponse: ctx.error_json,
|
||||
})
|
||||
)
|
||||
await ctx.controller.exportProject(ctx.req, ctx.res, ctx.next)
|
||||
expect(ctx.res.json.args[0][0]).to.deep.equal(ctx.error_json)
|
||||
expect(ctx.res.status.args[0][0]).to.equal(ctx.error_json.status)
|
||||
})
|
||||
})
|
||||
|
||||
it('should ask the handler to return the status of an export', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.handler.fetchExport = sinon.stub().yields(
|
||||
null,
|
||||
`{
|
||||
ctx.handler.fetchExport = sinon.stub().resolves(
|
||||
`{
|
||||
"id":897,
|
||||
"status_summary":"completed",
|
||||
"status_detail":"all done",
|
||||
@@ -190,26 +185,25 @@ describe('ExportsController', function () {
|
||||
"title":"my project",
|
||||
"token":"token"
|
||||
}`
|
||||
)
|
||||
)
|
||||
|
||||
ctx.req.params = { project_id: projectId, export_id: 897 }
|
||||
return ctx.controller.exportStatus(ctx.req, {
|
||||
json: body => {
|
||||
expect(body).to.deep.equal({
|
||||
export_json: {
|
||||
status_summary: 'completed',
|
||||
status_detail: 'all done',
|
||||
partner_submission_id: 'abc123',
|
||||
v2_user_email: 'la@tex.com',
|
||||
v2_user_first_name: 'Arthur',
|
||||
v2_user_last_name: 'Author',
|
||||
title: 'my project',
|
||||
token: 'token',
|
||||
},
|
||||
})
|
||||
return resolve()
|
||||
},
|
||||
})
|
||||
const res = {
|
||||
json: sinon.stub(),
|
||||
}
|
||||
|
||||
ctx.req.params = { project_id: projectId, export_id: 897 }
|
||||
await ctx.controller.exportStatus(ctx.req, res)
|
||||
expect(res.json.args[0][0]).to.deep.equal({
|
||||
export_json: {
|
||||
status_summary: 'completed',
|
||||
status_detail: 'all done',
|
||||
partner_submission_id: 'abc123',
|
||||
v2_user_email: 'la@tex.com',
|
||||
v2_user_first_name: 'Arthur',
|
||||
v2_user_last_name: 'Author',
|
||||
title: 'my project',
|
||||
token: 'token',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,50 +1,53 @@
|
||||
import { expect, vi } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import { RequestFailedError } from '@overleaf/fetch-utils'
|
||||
import OError from '@overleaf/o-error'
|
||||
const modulePath = '../../../../app/src/Features/Exports/ExportsHandler.mjs'
|
||||
|
||||
describe('ExportsHandler', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.stubRequest = {}
|
||||
ctx.request = {
|
||||
defaults: () => {
|
||||
return ctx.stubRequest
|
||||
},
|
||||
}
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
|
||||
default: (ctx.ProjectGetter = {}),
|
||||
default: (ctx.ProjectGetter = { promises: {} }),
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectHistoryHandler',
|
||||
() => ({
|
||||
default: (ctx.ProjectHistoryHandler = {}),
|
||||
default: (ctx.ProjectHistoryHandler = {
|
||||
promises: {},
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
ctx.fetchUtils = {
|
||||
fetchJson: sinon.stub().resolves(),
|
||||
fetchString: sinon.stub().resolves(),
|
||||
RequestFailedError,
|
||||
}
|
||||
|
||||
vi.doMock('@overleaf/fetch-utils', () => ctx.fetchUtils)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectLocator', () => ({
|
||||
default: (ctx.ProjectLocator = {}),
|
||||
default: (ctx.ProjectLocator = {
|
||||
promises: {},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectRootDocManager',
|
||||
() => ({
|
||||
default: (ctx.ProjectRootDocManager = {}),
|
||||
default: (ctx.ProjectRootDocManager = { promises: {} }),
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: (ctx.UserGetter = {}),
|
||||
default: (ctx.UserGetter = { promises: {} }),
|
||||
}))
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: (ctx.settings = {}),
|
||||
}))
|
||||
|
||||
vi.doMock('request', () => ({
|
||||
default: ctx.request,
|
||||
}))
|
||||
|
||||
ctx.ExportsHandler = (await import(modulePath)).default
|
||||
ctx.project_id = 'project-id-123'
|
||||
ctx.project_history_id = 987
|
||||
@@ -65,32 +68,22 @@ describe('ExportsHandler', function () {
|
||||
license: ctx.license,
|
||||
show_source: ctx.show_source,
|
||||
}
|
||||
ctx.callback = sinon.stub()
|
||||
})
|
||||
|
||||
describe('exportProject', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.export_data = { iAmAnExport: true }
|
||||
ctx.response_body = { iAmAResponseBody: true }
|
||||
ctx.ExportsHandler._buildExport = sinon
|
||||
.stub()
|
||||
.yields(null, ctx.export_data)
|
||||
ctx.ExportsHandler._buildExport = sinon.stub().resolves(ctx.export_data)
|
||||
ctx.ExportsHandler._requestExport = sinon
|
||||
.stub()
|
||||
.yields(null, ctx.response_body)
|
||||
.resolves(ctx.response_body)
|
||||
})
|
||||
|
||||
describe('when all goes well', function () {
|
||||
let exportData
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.ExportsHandler.exportProject(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
exportData = await ctx.ExportsHandler.exportProject(ctx.export_params)
|
||||
})
|
||||
|
||||
it('should build the export', function (ctx) {
|
||||
@@ -106,51 +99,46 @@ describe('ExportsHandler', function () {
|
||||
})
|
||||
|
||||
it('should return the export', function (ctx) {
|
||||
ctx.callback.calledWith(null, ctx.export_data).should.equal(true)
|
||||
expect(exportData).to.equal(ctx.export_data)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when request can't be built", function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.ExportsHandler._buildExport = sinon
|
||||
.stub()
|
||||
.yields(new Error('cannot export project without root doc'))
|
||||
ctx.ExportsHandler.exportProject(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
ctx.ExportsHandler._buildExport = sinon
|
||||
.stub()
|
||||
.rejects(new Error('cannot export project without root doc'))
|
||||
})
|
||||
|
||||
it('should return an error', function (ctx) {
|
||||
expect(ctx.callback.args[0][0]).to.be.instanceOf(Error)
|
||||
it('should reject with an error', async function (ctx) {
|
||||
await expect(
|
||||
ctx.ExportsHandler.exportProject(ctx.export_params)
|
||||
).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when export request returns an error to forward to the user', function () {
|
||||
let exportData
|
||||
let exportError
|
||||
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.error_json = { status: 422, message: 'nope' }
|
||||
ctx.ExportsHandler._requestExport = sinon
|
||||
.stub()
|
||||
.yields(null, { forwardResponse: ctx.error_json })
|
||||
ctx.ExportsHandler.exportProject(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
ctx.error_json = { status: 422, message: 'nope' }
|
||||
const error = new Error('error message')
|
||||
ctx.ExportsHandler._requestExport = sinon.stub().rejects(
|
||||
OError.tag(error, 'v1 error', {
|
||||
forwardResponse: ctx.error_json,
|
||||
})
|
||||
)
|
||||
try {
|
||||
exportData = await ctx.ExportsHandler.exportProject(ctx.export_params)
|
||||
} catch (error) {
|
||||
exportError = error
|
||||
}
|
||||
})
|
||||
|
||||
it('should return success and the response to forward', function (ctx) {
|
||||
expect(ctx.callback.args[0][0]).not.to.be.instanceOf(Error)
|
||||
ctx.callback.calledWith(null, {
|
||||
it('should throw with an error containing a response to forward', function (ctx) {
|
||||
expect(exportData).to.be.undefined
|
||||
expect(OError.getFullInfo(exportError)).to.eql({
|
||||
forwardResponse: ctx.error_json,
|
||||
})
|
||||
})
|
||||
@@ -159,63 +147,53 @@ describe('ExportsHandler', function () {
|
||||
|
||||
describe('_buildExport', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.project = {
|
||||
id: ctx.project_id,
|
||||
rootDoc_id: 'doc1_id',
|
||||
compiler: 'pdflatex',
|
||||
imageName: 'mock-image-name',
|
||||
overleaf: {
|
||||
id: ctx.project_history_id, // for projects imported from v1
|
||||
history: {
|
||||
id: ctx.project_history_id,
|
||||
},
|
||||
ctx.project = {
|
||||
id: ctx.project_id,
|
||||
rootDoc_id: 'doc1_id',
|
||||
compiler: 'pdflatex',
|
||||
imageName: 'mock-image-name',
|
||||
overleaf: {
|
||||
id: ctx.project_history_id, // for projects imported from v1
|
||||
history: {
|
||||
id: ctx.project_history_id,
|
||||
},
|
||||
}
|
||||
ctx.user = {
|
||||
id: ctx.user_id,
|
||||
first_name: 'Arthur',
|
||||
last_name: 'Author',
|
||||
email: 'arthur.author@arthurauthoring.org',
|
||||
overleaf: {
|
||||
id: 876,
|
||||
},
|
||||
}
|
||||
ctx.rootDocPath = 'main.tex'
|
||||
ctx.historyVersion = 777
|
||||
ctx.ProjectGetter.getProject = sinon.stub().yields(null, ctx.project)
|
||||
ctx.ProjectHistoryHandler.ensureHistoryExistsForProject = sinon
|
||||
.stub()
|
||||
.yields(null)
|
||||
ctx.ProjectLocator.findRootDoc = sinon
|
||||
.stub()
|
||||
.yields(null, [null, { fileSystem: 'main.tex' }])
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsValid = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null)
|
||||
ctx.UserGetter.getUser = sinon.stub().yields(null, ctx.user)
|
||||
ctx.ExportsHandler._requestVersion = sinon
|
||||
.stub()
|
||||
.yields(null, ctx.historyVersion)
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
}
|
||||
ctx.user = {
|
||||
id: ctx.user_id,
|
||||
first_name: 'Arthur',
|
||||
last_name: 'Author',
|
||||
email: 'arthur.author@arthurauthoring.org',
|
||||
overleaf: {
|
||||
id: 876,
|
||||
},
|
||||
}
|
||||
ctx.rootDocPath = 'main.tex'
|
||||
ctx.historyVersion = 777
|
||||
ctx.ProjectGetter.promises.getProject = sinon.stub().resolves(ctx.project)
|
||||
ctx.ProjectHistoryHandler.promises.ensureHistoryExistsForProject = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
ctx.ProjectLocator.promises.findRootDoc = sinon
|
||||
.stub()
|
||||
.resolves({ element: null, path: { fileSystem: 'main.tex' } })
|
||||
ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
ctx.UserGetter.promises.getUser = sinon.stub().resolves(ctx.user)
|
||||
ctx.ExportsHandler._requestVersion = sinon
|
||||
.stub()
|
||||
.resolves(ctx.historyVersion)
|
||||
})
|
||||
|
||||
describe('when all goes well', function () {
|
||||
let exportData
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.ExportsHandler._buildExport(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
exportData = await ctx.ExportsHandler._buildExport(ctx.export_params)
|
||||
})
|
||||
|
||||
it('should ensure the project has history', function (ctx) {
|
||||
ctx.ProjectHistoryHandler.ensureHistoryExistsForProject.called.should.equal(
|
||||
ctx.ProjectHistoryHandler.promises.ensureHistoryExistsForProject.called.should.equal(
|
||||
true
|
||||
)
|
||||
})
|
||||
@@ -257,25 +235,18 @@ describe('ExportsHandler', function () {
|
||||
callbackUrl: null,
|
||||
},
|
||||
}
|
||||
ctx.callback.calledWith(null, expectedExportData).should.equal(true)
|
||||
expect(exportData).to.eql(expectedExportData)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when we send replacement user first and last name', function () {
|
||||
let exportData
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.custom_first_name = 'FIRST'
|
||||
ctx.custom_last_name = 'LAST'
|
||||
ctx.export_params.first_name = ctx.custom_first_name
|
||||
ctx.export_params.last_name = ctx.custom_last_name
|
||||
ctx.ExportsHandler._buildExport(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
ctx.custom_first_name = 'FIRST'
|
||||
ctx.custom_last_name = 'LAST'
|
||||
ctx.export_params.first_name = ctx.custom_first_name
|
||||
ctx.export_params.last_name = ctx.custom_last_name
|
||||
exportData = await ctx.ExportsHandler._buildExport(ctx.export_params)
|
||||
})
|
||||
|
||||
it('should send the data from the user input', function (ctx) {
|
||||
@@ -311,51 +282,37 @@ describe('ExportsHandler', function () {
|
||||
callbackUrl: null,
|
||||
},
|
||||
}
|
||||
ctx.callback.calledWith(null, expectedExportData).should.equal(true)
|
||||
expect(exportData).to.eql(expectedExportData)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when project is not found', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.ProjectGetter.getProject = sinon
|
||||
.stub()
|
||||
.yields(new Error('project not found'))
|
||||
ctx.ExportsHandler._buildExport(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
ctx.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.rejects(new Error('project not found'))
|
||||
})
|
||||
|
||||
it('should return an error', function (ctx) {
|
||||
expect(ctx.callback.args[0][0]).to.be.instanceOf(Error)
|
||||
it('should return an error', async function (ctx) {
|
||||
await expect(
|
||||
ctx.ExportsHandler._buildExport(ctx.export_params)
|
||||
).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when project has no root doc', function () {
|
||||
describe('when a root doc can be set automatically', function () {
|
||||
let exportData
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.project.rootDoc_id = null
|
||||
ctx.ProjectLocator.findRootDoc = sinon
|
||||
.stub()
|
||||
.yields(null, [null, { fileSystem: 'other.tex' }])
|
||||
ctx.ExportsHandler._buildExport(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
ctx.project.rootDoc_id = null
|
||||
ctx.ProjectLocator.promises.findRootDoc = sinon
|
||||
.stub()
|
||||
.resolves({ path: { fileSystem: 'other.tex' } })
|
||||
exportData = await ctx.ExportsHandler._buildExport(ctx.export_params)
|
||||
})
|
||||
|
||||
it('should set a root doc', function (ctx) {
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsValid.called.should.equal(
|
||||
ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid.called.should.equal(
|
||||
true
|
||||
)
|
||||
})
|
||||
@@ -393,32 +350,25 @@ describe('ExportsHandler', function () {
|
||||
callbackUrl: null,
|
||||
},
|
||||
}
|
||||
ctx.callback.calledWith(null, expectedExportData).should.equal(true)
|
||||
expect(exportData).to.eql(expectedExportData)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when project has an invalid root doc', function () {
|
||||
describe('when a new root doc can be set automatically', function () {
|
||||
let exportData
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.fakeDoc_id = '1a2b3c4d5e6f'
|
||||
ctx.project.rootDoc_id = ctx.fakeDoc_id
|
||||
ctx.ProjectLocator.findRootDoc = sinon
|
||||
.stub()
|
||||
.yields(null, [null, { fileSystem: 'other.tex' }])
|
||||
ctx.ExportsHandler._buildExport(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
ctx.fakeDoc_id = '1a2b3c4d5e6f'
|
||||
ctx.project.rootDoc_id = ctx.fakeDoc_id
|
||||
ctx.ProjectLocator.promises.findRootDoc = sinon
|
||||
.stub()
|
||||
.resolves({ path: { fileSystem: 'other.tex' } })
|
||||
exportData = await ctx.ExportsHandler._buildExport(ctx.export_params)
|
||||
})
|
||||
|
||||
it('should set a valid root doc', function (ctx) {
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsValid.called.should.equal(
|
||||
ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid.called.should.equal(
|
||||
true
|
||||
)
|
||||
})
|
||||
@@ -456,281 +406,233 @@ describe('ExportsHandler', function () {
|
||||
callbackUrl: null,
|
||||
},
|
||||
}
|
||||
ctx.callback.calledWith(null, expectedExportData).should.equal(true)
|
||||
expect(exportData).to.eql(expectedExportData)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when no root doc can be identified', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.ProjectLocator.findRootDoc = sinon
|
||||
.stub()
|
||||
.yields(null, [null, null])
|
||||
ctx.ExportsHandler._buildExport(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
beforeEach(function (ctx) {
|
||||
ctx.ProjectLocator.promises.findRootDoc = sinon
|
||||
.stub()
|
||||
.resolves({ element: null, path: null })
|
||||
})
|
||||
|
||||
it('should return an error', function (ctx) {
|
||||
expect(ctx.callback.args[0][0]).to.be.instanceOf(Error)
|
||||
it('should return an error', async function (ctx) {
|
||||
await expect(
|
||||
ctx.ExportsHandler._buildExport(ctx.export_params)
|
||||
).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when user is not found', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.UserGetter.getUser = sinon
|
||||
.stub()
|
||||
.yields(new Error('user not found'))
|
||||
ctx.ExportsHandler._buildExport(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
ctx.UserGetter.promises.getUser = sinon
|
||||
.stub()
|
||||
.rejects(new Error('user not found'))
|
||||
})
|
||||
|
||||
it('should return an error', function (ctx) {
|
||||
expect(ctx.callback.args[0][0]).to.be.instanceOf(Error)
|
||||
it('should return an error', async function (ctx) {
|
||||
await expect(
|
||||
ctx.ExportsHandler._buildExport(ctx.export_params)
|
||||
).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when project history request fails', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.ExportsHandler._requestVersion = sinon
|
||||
.stub()
|
||||
.yields(new Error('project history call failed'))
|
||||
ctx.ExportsHandler._buildExport(
|
||||
ctx.export_params,
|
||||
(error, exportData) => {
|
||||
ctx.callback(error, exportData)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
beforeEach(function (ctx) {
|
||||
ctx.ExportsHandler._requestVersion = sinon
|
||||
.stub()
|
||||
.rejects(new Error('project history call failed'))
|
||||
})
|
||||
|
||||
it('should return an error', function (ctx) {
|
||||
expect(ctx.callback.args[0][0]).to.be.instanceOf(Error)
|
||||
it('should return an error', async function (ctx) {
|
||||
await expect(
|
||||
ctx.ExportsHandler._buildExport(ctx.export_params)
|
||||
).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_requestExport', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.settings.apis = {
|
||||
v1: {
|
||||
url: 'http://127.0.0.1:5000',
|
||||
user: 'overleaf',
|
||||
pass: 'pass',
|
||||
timeout: 15000,
|
||||
},
|
||||
}
|
||||
ctx.export_data = { iAmAnExport: true }
|
||||
ctx.export_id = 4096
|
||||
ctx.stubPost = sinon
|
||||
.stub()
|
||||
.yields(null, { statusCode: 200 }, { exportId: ctx.export_id })
|
||||
resolve()
|
||||
})
|
||||
beforeEach(function (ctx) {
|
||||
ctx.settings.apis = {
|
||||
v1: {
|
||||
url: 'http://127.0.0.1:5000',
|
||||
user: 'overleaf',
|
||||
pass: 'pass',
|
||||
timeout: 15000,
|
||||
},
|
||||
}
|
||||
ctx.export_data = { iAmAnExport: true }
|
||||
ctx.export_id = 4096
|
||||
ctx.fetchUtils.fetchJson.resolves({ exportId: ctx.export_id })
|
||||
})
|
||||
|
||||
describe('when all goes well', function () {
|
||||
let response
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.stubRequest.post = ctx.stubPost
|
||||
ctx.ExportsHandler._requestExport(
|
||||
ctx.export_data,
|
||||
(error, exportV1Id) => {
|
||||
ctx.callback(error, exportV1Id)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
response = await ctx.ExportsHandler._requestExport(ctx.export_data)
|
||||
})
|
||||
|
||||
it('should issue the request', function (ctx) {
|
||||
expect(ctx.stubPost.getCall(0).args[0]).to.deep.equal({
|
||||
url: ctx.settings.apis.v1.url + '/api/v1/overleaf/exports',
|
||||
auth: {
|
||||
user: ctx.settings.apis.v1.user,
|
||||
pass: ctx.settings.apis.v1.pass,
|
||||
},
|
||||
json: ctx.export_data,
|
||||
timeout: 15000,
|
||||
})
|
||||
expect(ctx.fetchUtils.fetchJson).to.have.been.calledWith(
|
||||
new URL('/api/v1/overleaf/exports', ctx.settings.apis.v1.url),
|
||||
{
|
||||
basicAuth: {
|
||||
user: ctx.settings.apis.v1.user,
|
||||
password: ctx.settings.apis.v1.pass,
|
||||
},
|
||||
json: ctx.export_data,
|
||||
method: 'POST',
|
||||
signal: sinon.match.instanceOf(AbortSignal),
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return the body with v1 export id', function (ctx) {
|
||||
ctx.callback
|
||||
.calledWith(null, { exportId: ctx.export_id })
|
||||
.should.equal(true)
|
||||
expect(response).to.eql({ exportId: ctx.export_id })
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the request fails', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.stubRequest.post = sinon
|
||||
.stub()
|
||||
.yields(new Error('export request failed'))
|
||||
ctx.ExportsHandler._requestExport(
|
||||
ctx.export_data,
|
||||
(error, exportV1Id) => {
|
||||
ctx.callback(error, exportV1Id)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fetchUtils.fetchJson = sinon
|
||||
.stub()
|
||||
.rejects(new Error('export request failed'))
|
||||
})
|
||||
|
||||
it('should return an error', function (ctx) {
|
||||
expect(ctx.callback.args[0][0]).to.be.instanceOf(Error)
|
||||
it('should return an error', async function (ctx) {
|
||||
await expect(
|
||||
ctx.ExportsHandler._requestExport(ctx.export_data)
|
||||
).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the request returns an error response to forward', function () {
|
||||
let response
|
||||
let error
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.error_code = 422
|
||||
ctx.error_json = { status: ctx.error_code, message: 'nope' }
|
||||
ctx.stubRequest.post = sinon
|
||||
ctx.fetchUtils.fetchJson = sinon
|
||||
.stub()
|
||||
.yields(null, { statusCode: ctx.error_code }, ctx.error_json)
|
||||
|
||||
await new Promise(resolve => {
|
||||
ctx.ExportsHandler._requestExport(
|
||||
ctx.export_data,
|
||||
(error, exportV1Id) => {
|
||||
ctx.callback(error, exportV1Id)
|
||||
resolve()
|
||||
}
|
||||
.rejects(
|
||||
new RequestFailedError(
|
||||
new URL('/api/v1/overleaf/exports', ctx.settings.apis.v1.url),
|
||||
{},
|
||||
{ status: ctx.error_code },
|
||||
ctx.error_json
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
try {
|
||||
response = await ctx.ExportsHandler._requestExport(ctx.export_data)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
})
|
||||
|
||||
it('should return success and the response to forward', function (ctx) {
|
||||
expect(ctx.callback.args[0][0]).not.to.be.instanceOf(Error)
|
||||
ctx.callback.calledWith(null, {
|
||||
forwardResponse: ctx.error_json,
|
||||
})
|
||||
it('should throw with an error containing the response to forward', function (ctx) {
|
||||
expect(response).to.be.undefined
|
||||
expect(OError.getFullInfo(error)).to.have.property(
|
||||
'forwardResponse',
|
||||
ctx.error_json
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchExport', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.settings.apis = {
|
||||
v1: {
|
||||
url: 'http://127.0.0.1:5000',
|
||||
user: 'overleaf',
|
||||
pass: 'pass',
|
||||
timeout: 15000,
|
||||
},
|
||||
}
|
||||
ctx.export_id = 897
|
||||
ctx.body = '{"id":897, "status_summary":"completed"}'
|
||||
ctx.stubGet = sinon
|
||||
.stub()
|
||||
.yields(null, { statusCode: 200 }, { body: ctx.body })
|
||||
resolve()
|
||||
})
|
||||
beforeEach(function (ctx) {
|
||||
ctx.settings.apis = {
|
||||
v1: {
|
||||
url: 'http://127.0.0.1:5000',
|
||||
user: 'overleaf',
|
||||
pass: 'pass',
|
||||
timeout: 15000,
|
||||
},
|
||||
}
|
||||
ctx.export_id = 897
|
||||
ctx.body = '{"id":897, "status_summary":"completed"}'
|
||||
ctx.fetchUtils.fetchString = sinon
|
||||
.stub()
|
||||
.resolves(JSON.stringify({ body: ctx.body }))
|
||||
})
|
||||
|
||||
describe('when all goes well', function () {
|
||||
let exportResponse
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.stubRequest.get = ctx.stubGet
|
||||
ctx.ExportsHandler.fetchExport(ctx.export_id, (error, body) => {
|
||||
ctx.callback(error, body)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
exportResponse = await ctx.ExportsHandler.fetchExport(ctx.export_id)
|
||||
})
|
||||
|
||||
it('should issue the request', function (ctx) {
|
||||
expect(ctx.stubGet.getCall(0).args[0]).to.deep.equal({
|
||||
url:
|
||||
ctx.settings.apis.v1.url +
|
||||
'/api/v1/overleaf/exports/' +
|
||||
ctx.export_id,
|
||||
auth: {
|
||||
user: ctx.settings.apis.v1.user,
|
||||
pass: ctx.settings.apis.v1.pass,
|
||||
},
|
||||
timeout: 15000,
|
||||
})
|
||||
expect(ctx.fetchUtils.fetchString).to.have.been.calledWith(
|
||||
new URL(
|
||||
'/api/v1/overleaf/exports/' + ctx.export_id,
|
||||
ctx.settings.apis.v1.url
|
||||
),
|
||||
{
|
||||
basicAuth: {
|
||||
user: ctx.settings.apis.v1.user,
|
||||
password: ctx.settings.apis.v1.pass,
|
||||
},
|
||||
signal: sinon.match.instanceOf(AbortSignal),
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return the v1 export id', function (ctx) {
|
||||
ctx.callback.calledWith(null, { body: ctx.body }).should.equal(true)
|
||||
expect(exportResponse).to.eql(JSON.stringify({ body: ctx.body }))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchDownload', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.settings.apis = {
|
||||
v1: {
|
||||
url: 'http://127.0.0.1:5000',
|
||||
user: 'overleaf',
|
||||
pass: 'pass',
|
||||
timeout: 15000,
|
||||
},
|
||||
}
|
||||
ctx.export_id = 897
|
||||
ctx.body =
|
||||
'https://writelatex-conversions-dev.s3.amazonaws.com/exports/ieee_latexqc/tnb/2912/xggmprcrpfwbsnqzqqmvktddnrbqkqkr.zip?X-Amz-Expires=14400&X-Amz-Date=20180730T181003Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJDGDIJFGLNVGZH6A/20180730/us-east-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=dec990336913cef9933f0e269afe99722d7ab2830ebf2c618a75673ee7159fee'
|
||||
ctx.stubGet = sinon
|
||||
.stub()
|
||||
.yields(null, { statusCode: 200 }, { body: ctx.body })
|
||||
resolve()
|
||||
})
|
||||
ctx.settings.apis = {
|
||||
v1: {
|
||||
url: 'http://127.0.0.1:5000',
|
||||
user: 'overleaf',
|
||||
pass: 'pass',
|
||||
timeout: 15000,
|
||||
},
|
||||
}
|
||||
ctx.export_id = 897
|
||||
ctx.body =
|
||||
'https://writelatex-conversions-dev.s3.amazonaws.com/exports/ieee_latexqc/tnb/2912/xggmprcrpfwbsnqzqqmvktddnrbqkqkr.zip?X-Amz-Expires=14400&X-Amz-Date=20180730T181003Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJDGDIJFGLNVGZH6A/20180730/us-east-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=dec990336913cef9933f0e269afe99722d7ab2830ebf2c618a75673ee7159fee'
|
||||
ctx.fetchUtils.fetchString = sinon.stub().resolves(ctx.body)
|
||||
})
|
||||
|
||||
describe('when all goes well', function () {
|
||||
let downloadResponse
|
||||
beforeEach(async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.stubRequest.get = ctx.stubGet
|
||||
ctx.ExportsHandler.fetchDownload(
|
||||
ctx.export_id,
|
||||
'zip',
|
||||
(error, body) => {
|
||||
ctx.callback(error, body)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
downloadResponse = await ctx.ExportsHandler.fetchDownload(
|
||||
ctx.export_id,
|
||||
'zip'
|
||||
)
|
||||
})
|
||||
|
||||
it('should issue the request', function (ctx) {
|
||||
expect(ctx.stubGet.getCall(0).args[0]).to.deep.equal({
|
||||
url:
|
||||
expect(ctx.fetchUtils.fetchString).to.have.been.calledWith(
|
||||
new URL(
|
||||
ctx.settings.apis.v1.url +
|
||||
'/api/v1/overleaf/exports/' +
|
||||
ctx.export_id +
|
||||
'/zip_url',
|
||||
auth: {
|
||||
user: ctx.settings.apis.v1.user,
|
||||
pass: ctx.settings.apis.v1.pass,
|
||||
},
|
||||
timeout: 15000,
|
||||
})
|
||||
'/api/v1/overleaf/exports/' +
|
||||
ctx.export_id +
|
||||
'/zip_url'
|
||||
),
|
||||
{
|
||||
basicAuth: {
|
||||
user: ctx.settings.apis.v1.user,
|
||||
password: ctx.settings.apis.v1.pass,
|
||||
},
|
||||
signal: sinon.match.instanceOf(AbortSignal),
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return the v1 export id', function (ctx) {
|
||||
ctx.callback.calledWith(null, { body: ctx.body }).should.equal(true)
|
||||
expect(downloadResponse).to.eql(ctx.body)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user