ClsiManagerTests.coffee 11 KB

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