ClsiManagerTests.coffee 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. expect = chai.expect
  5. modulePath = "../../../../app/js/Features/Compile/ClsiManager.js"
  6. SandboxedModule = require('sandboxed-module')
  7. describe "ClsiManager", ->
  8. beforeEach ->
  9. @ClsiManager = SandboxedModule.require modulePath, requires:
  10. "settings-sharelatex": @settings =
  11. apis:
  12. filestore:
  13. url: "filestore.example.com"
  14. secret: "secret"
  15. clsi:
  16. url: "http://clsi.example.com"
  17. clsi_priority:
  18. url: "https://clsipremium.example.com"
  19. "../../models/Project": Project: @Project = {}
  20. "../Project/ProjectEntityHandler": @ProjectEntityHandler = {}
  21. "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
  22. "request": @request = {}
  23. @project_id = "project-id"
  24. @callback = sinon.stub()
  25. describe "sendRequest", ->
  26. beforeEach ->
  27. @ClsiManager._buildRequest = sinon.stub().callsArgWith(2, null, @request = "mock-request")
  28. describe "with a successful compile", ->
  29. beforeEach ->
  30. @ClsiManager._postToClsi = sinon.stub().callsArgWith(3, null, {
  31. compile:
  32. status: @status = "success"
  33. outputFiles: [{
  34. url: "#{@settings.apis.clsi.url}/project/#{@project_id}/output/output.pdf"
  35. type: "pdf"
  36. },{
  37. url: "#{@settings.apis.clsi.url}/project/#{@project_id}/output/output.log"
  38. type: "log"
  39. }]
  40. })
  41. @ClsiManager.sendRequest @project_id, {compileGroup:"standard"}, @callback
  42. it "should build the request", ->
  43. @ClsiManager._buildRequest
  44. .calledWith(@project_id)
  45. .should.equal true
  46. it "should send the request to the CLSI", ->
  47. @ClsiManager._postToClsi
  48. .calledWith(@project_id, @request, "standard")
  49. .should.equal true
  50. it "should call the callback with the status and output files", ->
  51. outputFiles = [{
  52. path: "output.pdf"
  53. type: "pdf"
  54. },{
  55. path: "output.log"
  56. type: "log"
  57. }]
  58. @callback.calledWith(null, @status, outputFiles).should.equal true
  59. describe "with a failed compile", ->
  60. beforeEach ->
  61. @ClsiManager._postToClsi = sinon.stub().callsArgWith(3, null, {
  62. compile:
  63. status: @status = "failure"
  64. })
  65. @ClsiManager.sendRequest @project_id, {}, @callback
  66. it "should call the callback with a failure statue", ->
  67. @callback.calledWith(null, @status).should.equal true
  68. describe "deleteAuxFiles", ->
  69. beforeEach ->
  70. @request.del = sinon.stub().callsArg(1)
  71. describe "with the standard compileGroup", ->
  72. beforeEach ->
  73. @ClsiManager.deleteAuxFiles @project_id, {compileGroup: "standard"}, @callback
  74. it "should call the delete method in the standard CLSI", ->
  75. @request.del
  76. .calledWith("#{@settings.apis.clsi.url}/project/#{@project_id}")
  77. .should.equal true
  78. it "should call the callback", ->
  79. @callback.called.should.equal true
  80. describe "with the priority compileGroup", ->
  81. beforeEach ->
  82. @ClsiManager.deleteAuxFiles @project_id, {compileGroup: "priority"}, @callback
  83. it "should call the delete method in the CLSI", ->
  84. @request.del
  85. .calledWith("#{@settings.apis.clsi_priority.url}/project/#{@project_id}")
  86. .should.equal true
  87. describe "_buildRequest", ->
  88. beforeEach ->
  89. @project =
  90. _id: @project_id
  91. compiler: @compiler = "latex"
  92. rootDoc_id: "mock-doc-id-1"
  93. @docs = {
  94. "/main.tex": @doc_1 = {
  95. name: "main.tex"
  96. _id: "mock-doc-id-1"
  97. lines: ["Hello", "world"]
  98. },
  99. "/chapters/chapter1.tex": @doc_2 = {
  100. name: "chapter1.tex"
  101. _id: "mock-doc-id-2"
  102. lines: [
  103. "Chapter 1"
  104. ]
  105. }
  106. }
  107. @files = {
  108. "/images/image.png": @file_1 = {
  109. name: "image.png"
  110. _id: "mock-file-id-1"
  111. created: new Date()
  112. }
  113. }
  114. @Project.findById = sinon.stub().callsArgWith(2, null, @project)
  115. @ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @docs)
  116. @ProjectEntityHandler.getAllFiles = sinon.stub().callsArgWith(1, null, @files)
  117. describe "with a valid project", ->
  118. beforeEach (done) ->
  119. @ClsiManager._buildRequest @project_id, {timeout:100}, (error, request) =>
  120. @request = request
  121. done()
  122. it "should get the project with the required fields", ->
  123. @Project.findById
  124. .calledWith(@project_id, {compiler:1, rootDoc_id: 1})
  125. .should.equal true
  126. it "should get all the docs", ->
  127. @ProjectEntityHandler.getAllDocs
  128. .calledWith(@project_id)
  129. .should.equal true
  130. it "should get all the files", ->
  131. @ProjectEntityHandler.getAllFiles
  132. .calledWith(@project_id)
  133. .should.equal true
  134. it "should build up the CLSI request", ->
  135. expect(@request).to.deep.equal(
  136. compile:
  137. options:
  138. compiler: @compiler
  139. timeout : 100
  140. rootResourcePath: "main.tex"
  141. resources: [{
  142. path: "main.tex"
  143. content: @doc_1.lines.join("\n")
  144. }, {
  145. path: "chapters/chapter1.tex"
  146. content: @doc_2.lines.join("\n")
  147. }, {
  148. path: "images/image.png"
  149. url: "#{@settings.apis.filestore.url}/project/#{@project_id}/file/#{@file_1._id}"
  150. modified: @file_1.created.getTime()
  151. }]
  152. )
  153. describe "when root doc override is valid", ->
  154. beforeEach (done) ->
  155. @ClsiManager._buildRequest @project_id, {rootDoc_id:"mock-doc-id-2"}, (error, request) =>
  156. @request = request
  157. done()
  158. it "should change root path", ->
  159. @request.compile.rootResourcePath.should.equal "chapters/chapter1.tex"
  160. describe "when root doc override is invalid", ->
  161. beforeEach (done) ->
  162. @ClsiManager._buildRequest @project_id, {rootDoc_id:"invalid-id"}, (error, request) =>
  163. @request = request
  164. done()
  165. it "should fallback to default root doc", ->
  166. @request.compile.rootResourcePath.should.equal "main.tex"
  167. describe "when the project has an invalid compiler", ->
  168. beforeEach (done) ->
  169. @project.compiler = "context"
  170. @ClsiManager._buildRequest @project, null, (error, request) =>
  171. @request = request
  172. done()
  173. it "should set the compiler to pdflatex", ->
  174. @request.compile.options.compiler.should.equal "pdflatex"
  175. describe "when there is no valid root document", ->
  176. beforeEach (done) ->
  177. @project.rootDoc_id = "not-valid"
  178. @ClsiManager._buildRequest @project, null, (@error, @request) =>
  179. done()
  180. it "should return an error", ->
  181. expect(@error).to.exist
  182. describe '_postToClsi', ->
  183. beforeEach ->
  184. @req = { mock: "req" }
  185. describe "successfully", ->
  186. beforeEach ->
  187. @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204}, @body = { mock: "foo" })
  188. @ClsiManager._postToClsi @project_id, @req, "standard", @callback
  189. it 'should send the request to the CLSI', ->
  190. url = "#{@settings.apis.clsi.url}/project/#{@project_id}/compile"
  191. @request.post.calledWith({
  192. url: url
  193. json: @req
  194. jar: false
  195. }).should.equal true
  196. it "should call the callback with the body and no error", ->
  197. @callback.calledWith(null, @body).should.equal true
  198. describe "when the CLSI returns an error", ->
  199. beforeEach ->
  200. @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 500}, @body = { mock: "foo" })
  201. @ClsiManager._postToClsi @project_id, @req, "standard", @callback
  202. it "should call the callback with the body and the error", ->
  203. @callback.calledWith(new Error("CLSI returned non-success code: 500"), @body).should.equal true
  204. describe "when the compiler is priority", ->
  205. beforeEach ->
  206. @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 500}, @body = { mock: "foo" })
  207. @ClsiManager._postToClsi @project_id, @req, "priority", @callback
  208. it "should use the clsi_priority url", ->
  209. url = "#{@settings.apis.clsi_priority.url}/project/#{@project_id}/compile"
  210. @request.post.calledWith({
  211. url: url
  212. json: @req
  213. jar: false
  214. }).should.equal true