ClsiManagerTests.coffee 10.0 KB

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