Просмотр исходного кода

Add ExportsHander for project exports performed via v1

Michael Mazour 8 лет назад
Родитель
Сommit
a661084485

+ 93 - 0
services/web/app/coffee/Features/Exports/ExportsHandler.coffee

@@ -0,0 +1,93 @@
+ProjectGetter = require('../Project/ProjectGetter')
+ProjectLocator = require('../Project/ProjectLocator')
+UserGetter = require('../User/UserGetter')
+logger = require('logger-sharelatex')
+settings = require 'settings-sharelatex'
+async = require 'async'
+request = require 'request'
+request = request.defaults()
+settings = require 'settings-sharelatex'
+
+module.exports = ExportsHandler = self =
+
+	exportProject: (project_id, user_id, brand_variation_id, callback=(error, export_data) ->) ->
+		self._buildExport project_id, user_id, brand_variation_id, (err, export_data) ->
+			return callback(err) if err?
+			self._requestExport export_data, (err, export_v1_id) ->
+				return callback(err) if err?
+				export_data.v1_id = export_v1_id
+				# TODO: possibly store the export data in Mongo
+				callback null, export_data
+
+	_buildExport: (project_id, user_id, brand_variation_id, callback=(err, export_data) ->) ->
+		jobs =
+			project: (cb) ->
+				ProjectGetter.getProject project_id, cb
+			# TODO: when we update async, signature will change from (cb, results) to (results, cb)
+			rootDoc: [ 'project', (cb, results) ->
+				ProjectLocator.findRootDoc {project: results.project, project_id: project_id}, cb
+			]
+			user: (cb) ->
+				UserGetter.getUser user_id, {first_name: 1, last_name: 1, email: 1}, cb
+			historyVersion: (cb) ->
+				self._requestVersion project_id, cb
+
+		async.auto jobs, (err, results) ->
+			if err?
+				logger.err err:err, project_id:project_id, user_id:user_id, brand_variation_id:brand_variation_id, "error building project export"
+				return callback(err)
+
+			{project, rootDoc, user, historyVersion} = results
+			if !rootDoc[1]?
+				err = new Error("cannot export project without root doc")
+				logger.err err:err, project_id: project_id
+				return callback(err)
+
+			export_data =
+				project:
+					id: project_id
+					rootDocPath: rootDoc[1]?.fileSystem
+					historyId: project.overleaf?.history?.id
+					historyVersion: historyVersion
+				user:
+					id: user_id
+					firstName: user.first_name
+					lastName: user.last_name
+					email: user.email
+					orcidId: null # until v2 gets ORCID
+				destination:
+					brandVariationId: brand_variation_id
+				options:
+					callbackUrl: null # for now, until we want v1 to call us back
+			callback null, export_data
+
+	_requestExport: (export_data, callback=(err, export_v1_id) ->) ->
+		request.post {
+			url: "#{settings.apis.v1.url}/api/v1/sharelatex/exports"
+			auth: {user: settings.apis.v1.user, pass: settings.apis.v1.pass }
+			json: export_data
+		}, (err, res, body) ->
+			if err?
+				logger.err err:err, export:export_data, "error making request to v1 export"
+				callback err
+			else if 200 <= res.statusCode < 300
+				callback null, body.exportId
+			else
+				err = new Error("v1 export returned a failure status code: #{res.statusCode}")
+				logger.err err:err, export:export_data, "v1 export returned failure status code: #{res.statusCode}"
+				callback err
+
+	_requestVersion: (project_id, callback=(err, export_v1_id) ->) ->
+		request.get {
+			url: "#{settings.apis.project_history.url}/project/#{project_id}/version"
+			json: true
+		}, (err, res, body) ->
+			if err?
+				logger.err err:err, project_id:project_id, "error making request to project history"
+				callback err
+			else if res.statusCode >= 200 and res.statusCode < 300
+				callback null, body.version
+			else
+				err = new Error("project history version returned a failure status code: #{res.statusCode}")
+				logger.err err:err, project_id:project_id, "project history version returned failure status code: #{res.statusCode}"
+				callback err

+ 202 - 0
services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee

@@ -0,0 +1,202 @@
+sinon = require('sinon')
+chai = require('chai')
+should = chai.should()
+expect = chai.expect
+modulePath = '../../../../app/js/Features/Exports/ExportsHandler.js'
+SandboxedModule = require('sandboxed-module')
+
+describe 'ExportsHandler', ->
+
+	beforeEach ->
+		@ProjectGetter = {}
+		@ProjectLocator = {}
+		@UserGetter = {}
+		@settings = {}
+		@stubRequest = {}
+		@request = defaults: => return @stubRequest
+		@ExportsHandler = SandboxedModule.require modulePath, requires:
+			'logger-sharelatex':
+				log: ->
+				err: ->
+			'../Project/ProjectGetter': @ProjectGetter
+			'../Project/ProjectLocator': @ProjectLocator
+			'../User/UserGetter': @UserGetter
+			'settings-sharelatex': @settings
+			'request': @request
+		@project_id = "project-id-123"
+		@project_history_id = 987
+		@user_id = "user-id-456"
+		@brand_variation_id = 789
+		@callback = sinon.stub()
+
+	describe 'exportProject', ->
+		beforeEach (done) ->
+			@export_data = {iAmAnExport: true}
+			@response_body = {iAmAResponseBody: true}
+			@ExportsHandler._buildExport = sinon.stub().yields(null, @export_data)
+			@ExportsHandler._requestExport = sinon.stub().yields(null, @response_body)
+			@ExportsHandler.exportProject @project_id, @user_id, @brand_variation_id, (error, export_data) =>
+				@callback(error, export_data)
+				done()
+
+		it "should build the export", ->
+			@ExportsHandler._buildExport
+			.calledWith(@project_id, @user_id, @brand_variation_id)
+			.should.equal true
+
+		it "should request the export", ->
+			@ExportsHandler._requestExport
+			.calledWith(@export_data)
+			.should.equal true
+
+		it "should return the export", ->
+			@callback
+			.calledWith(null, @export_data)
+			.should.equal true
+
+	describe '_buildExport', ->
+		beforeEach (done) ->
+			@project =
+				id: @project_id
+				overleaf:
+					history:
+						id: @project_history_id
+			@user =
+				id: @user_id
+				first_name: 'Arthur'
+				last_name: 'Author'
+				email: 'arthur.author@arthurauthoring.org'
+			@rootDocPath = 'main.tex'
+			@historyVersion = 777
+			@ProjectGetter.getProject = sinon.stub().yields(null, @project)
+			@ProjectLocator.findRootDoc = sinon.stub().yields(null, [null, {fileSystem: 'main.tex'}])
+			@UserGetter.getUser = sinon.stub().yields(null, @user)
+			@ExportsHandler._requestVersion = sinon.stub().yields(null, @historyVersion)
+			done()
+
+		describe "when all goes well", ->
+			beforeEach (done) ->
+				@ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) =>
+					@callback(error, export_data)
+					done()
+
+			it "should request the project history version", ->
+				@ExportsHandler._requestVersion.called
+				.should.equal true
+
+			it "should return export data", ->
+				expected_export_data = 
+					project:
+						id: @project_id
+						rootDocPath: @rootDocPath
+						historyId: @project_history_id
+						historyVersion: @historyVersion
+					user:
+						id: @user_id
+						firstName: @user.first_name
+						lastName: @user.last_name
+						email: @user.email
+						orcidId: null
+					destination:
+						brandVariationId: @brand_variation_id
+					options:
+						callbackUrl: null
+				@callback.calledWith(null, expected_export_data)
+				.should.equal true
+
+		describe "when project is not found", ->
+			beforeEach (done) ->
+				@ProjectGetter.getProject = sinon.stub().yields(new Error("project not found"))
+				@ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) =>
+					@callback(error, export_data)
+					done()
+
+			it "should return an error", ->
+				(@callback.args[0][0] instanceof Error)
+				.should.equal true
+
+		describe "when project has no root doc", ->
+			beforeEach (done) ->
+				@ProjectLocator.findRootDoc = sinon.stub().yields(null, [null, null])
+				@ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) =>
+					@callback(error, export_data)
+					done()
+
+			it "should return an error", ->
+				(@callback.args[0][0] instanceof Error)
+				.should.equal true
+
+		describe "when user is not found", ->
+			beforeEach (done) ->
+				@UserGetter.getUser = sinon.stub().yields(new Error("user not found"))
+				@ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) =>
+					@callback(error, export_data)
+					done()
+
+			it "should return an error", ->
+				(@callback.args[0][0] instanceof Error)
+				.should.equal true
+
+		describe "when project history request fails", ->
+			beforeEach (done) ->
+				@ExportsHandler._requestVersion = sinon.stub().yields(new Error("project history call failed"))
+				@ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) =>
+					@callback(error, export_data)
+					done()
+
+			it "should return an error", ->
+				(@callback.args[0][0] instanceof Error)
+				.should.equal true
+
+	describe '_requestExport', ->
+		beforeEach (done) ->
+			@settings.apis =
+				v1:
+					url: 'http://localhost:5000'
+					user: 'overleaf'
+					pass: 'pass'
+			@export_data = {iAmAnExport: true}
+			@export_id = 4096
+			@stubPost = sinon.stub().yields(null, {statusCode: 200}, { exportId: @export_id })
+			done()
+
+		describe "when all goes well", ->
+			beforeEach (done) ->
+				@stubRequest.post = @stubPost
+				@ExportsHandler._requestExport @export_data, (error, export_v1_id) =>
+					@callback(error, export_v1_id)
+					done()
+
+			it 'should issue the request', ->
+				expect(@stubPost.getCall(0).args[0]).to.deep.equal
+					url: @settings.apis.v1.url + '/api/v1/sharelatex/exports'
+					auth:
+						user: @settings.apis.v1.user
+						pass: @settings.apis.v1.pass
+					json: @export_data
+
+			it 'should return the v1 export id', ->
+				@callback.calledWith(null, @export_id)
+				.should.equal true
+
+		describe "when the request fails", ->
+			beforeEach (done) ->
+				@stubRequest.post = sinon.stub().yields(new Error("export request failed"))
+				@ExportsHandler._requestExport @export_data, (error, export_v1_id) =>
+					@callback(error, export_v1_id)
+					done()
+
+			it "should return an error", ->
+				(@callback.args[0][0] instanceof Error)
+				.should.equal true
+
+		describe "when the request returns an error code", ->
+			beforeEach (done) ->
+				@stubRequest.post = sinon.stub().yields(null, {statusCode: 401}, { })
+				@ExportsHandler._requestExport @export_data, (error, export_v1_id) =>
+					@callback(error, export_v1_id)
+					done()
+
+			it "should return the error", ->
+				(@callback.args[0][0] instanceof Error)
+				.should.equal true