ClsiManagerTests.coffee 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  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. _getServerId:sinon.stub()
  14. @ClsiStateManager =
  15. computeHash: sinon.stub().callsArgWith(2, null, "01234567890abcdef")
  16. @ClsiFormatChecker =
  17. checkRecoursesForProblems:sinon.stub().callsArgWith(1)
  18. @ClsiManager = SandboxedModule.require modulePath, requires:
  19. "settings-sharelatex": @settings =
  20. apis:
  21. filestore:
  22. url: "filestore.example.com"
  23. secret: "secret"
  24. clsi:
  25. url: "http://clsi.example.com"
  26. clsi_priority:
  27. url: "https://clsipremium.example.com"
  28. "../../models/Project": Project: @Project = {}
  29. "../Project/ProjectEntityHandler": @ProjectEntityHandler = {}
  30. "../Project/ProjectGetter": @ProjectGetter = {}
  31. "../DocumentUpdater/DocumentUpdaterHandler": @DocumentUpdaterHandler =
  32. getProjectDocsIfMatch: sinon.stub().callsArgWith(2,null,null)
  33. "./ClsiCookieManager": => @ClsiCookieManager
  34. "./ClsiStateManager": @ClsiStateManager
  35. "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), err: sinon.stub(), warn: sinon.stub() }
  36. "request": @request = sinon.stub()
  37. "./ClsiFormatChecker": @ClsiFormatChecker
  38. "metrics-sharelatex": @Metrics =
  39. Timer: class Timer
  40. done: sinon.stub()
  41. inc: sinon.stub()
  42. @project_id = "project-id"
  43. @user_id = "user-id"
  44. @callback = sinon.stub()
  45. describe "sendRequest", ->
  46. beforeEach ->
  47. @ClsiManager._buildRequest = sinon.stub().callsArgWith(2, null, @request = "mock-request")
  48. @ClsiCookieManager._getServerId.callsArgWith(1, null, "clsi3")
  49. describe "with a successful compile", ->
  50. beforeEach ->
  51. @ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
  52. compile:
  53. status: @status = "success"
  54. outputFiles: [{
  55. url: "#{@settings.apis.clsi.url}/project/#{@project_id}/user/#{@user_id}/build/1234/output/output.pdf"
  56. path: "output.pdf"
  57. type: "pdf"
  58. build: 1234
  59. },{
  60. url: "#{@settings.apis.clsi.url}/project/#{@project_id}/user/#{@user_id}/build/1234/output/output.log"
  61. path: "output.log"
  62. type: "log"
  63. build: 1234
  64. }]
  65. })
  66. @ClsiManager.sendRequest @project_id, @user_id, {compileGroup:"standard"}, @callback
  67. it "should build the request", ->
  68. @ClsiManager._buildRequest
  69. .calledWith(@project_id)
  70. .should.equal true
  71. it "should send the request to the CLSI", ->
  72. @ClsiManager._postToClsi
  73. .calledWith(@project_id, @user_id, @request, "standard")
  74. .should.equal true
  75. it "should call the callback with the status and output files", ->
  76. outputFiles = [{
  77. url: "/project/#{@project_id}/user/#{@user_id}/build/1234/output/output.pdf"
  78. path: "output.pdf"
  79. type: "pdf"
  80. build: 1234
  81. },{
  82. url: "/project/#{@project_id}/user/#{@user_id}/build/1234/output/output.log"
  83. path: "output.log"
  84. type: "log"
  85. build: 1234
  86. }]
  87. @callback.calledWith(null, @status, outputFiles).should.equal true
  88. describe "with a failed compile", ->
  89. beforeEach ->
  90. @ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
  91. compile:
  92. status: @status = "failure"
  93. })
  94. @ClsiManager.sendRequest @project_id, @user_id, {}, @callback
  95. it "should call the callback with a failure status", ->
  96. @callback.calledWith(null, @status).should.equal true
  97. describe "with a sync conflict", ->
  98. beforeEach ->
  99. @ClsiManager.sendRequestOnce = sinon.stub()
  100. @ClsiManager.sendRequestOnce.withArgs(@project_id, @user_id, {syncType:"full"}).callsArgWith(3, null, @status = "success")
  101. @ClsiManager.sendRequestOnce.withArgs(@project_id, @user_id, {}).callsArgWith(3, null, "conflict")
  102. @ClsiManager.sendRequest @project_id, @user_id, {}, @callback
  103. it "should call the sendRequestOnce method twice", ->
  104. @ClsiManager.sendRequestOnce.calledTwice.should.equal true
  105. it "should call the sendRequestOnce method with syncType:full", ->
  106. @ClsiManager.sendRequestOnce.calledWith(@project_id, @user_id, {syncType:"full"}).should.equal true
  107. it "should call the sendRequestOnce method without syncType:full", ->
  108. @ClsiManager.sendRequestOnce.calledWith(@project_id, @user_id, {}).should.equal true
  109. it "should call the callback with a success status", ->
  110. @callback.calledWith(null, @status, ).should.equal true
  111. describe "when the resources fail the precompile check", ->
  112. beforeEach ->
  113. @ClsiFormatChecker.checkRecoursesForProblems = sinon.stub().callsArgWith(1, new Error("failed"))
  114. @ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
  115. compile:
  116. status: @status = "failure"
  117. })
  118. @ClsiManager.sendRequest @project_id, @user_id, {}, @callback
  119. it "should call the callback only once", ->
  120. @callback.calledOnce.should.equal true
  121. it "should call the callback with an error", ->
  122. @callback.calledWithExactly(new Error("failed")).should.equal true
  123. describe "sendExternalRequest", ->
  124. beforeEach ->
  125. @submission_id = "submission-id"
  126. @clsi_request = "mock-request"
  127. @ClsiCookieManager._getServerId.callsArgWith(1, null, "clsi3")
  128. describe "with a successful compile", ->
  129. beforeEach ->
  130. @ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
  131. compile:
  132. status: @status = "success"
  133. outputFiles: [{
  134. url: "#{@settings.apis.clsi.url}/project/#{@submission_id}/build/1234/output/output.pdf"
  135. path: "output.pdf"
  136. type: "pdf"
  137. build: 1234
  138. },{
  139. url: "#{@settings.apis.clsi.url}/project/#{@submission_id}/build/1234/output/output.log"
  140. path: "output.log"
  141. type: "log"
  142. build: 1234
  143. }]
  144. })
  145. @ClsiManager.sendExternalRequest @submission_id, @clsi_request, {compileGroup:"standard"}, @callback
  146. it "should send the request to the CLSI", ->
  147. @ClsiManager._postToClsi
  148. .calledWith(@submission_id, null, @clsi_request, "standard")
  149. .should.equal true
  150. it "should call the callback with the status and output files", ->
  151. outputFiles = [{
  152. url: "/project/#{@submission_id}/build/1234/output/output.pdf"
  153. path: "output.pdf"
  154. type: "pdf"
  155. build: 1234
  156. },{
  157. url: "/project/#{@submission_id}/build/1234/output/output.log"
  158. path: "output.log"
  159. type: "log"
  160. build: 1234
  161. }]
  162. @callback.calledWith(null, @status, outputFiles).should.equal true
  163. describe "with a failed compile", ->
  164. beforeEach ->
  165. @ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
  166. compile:
  167. status: @status = "failure"
  168. })
  169. @ClsiManager.sendExternalRequest @submission_id, @clsi_request, {}, @callback
  170. it "should call the callback with a failure status", ->
  171. @callback.calledWith(null, @status).should.equal true
  172. describe "when the resources fail the precompile check", ->
  173. beforeEach ->
  174. @ClsiFormatChecker.checkRecoursesForProblems = sinon.stub().callsArgWith(1, new Error("failed"))
  175. @ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
  176. compile:
  177. status: @status = "failure"
  178. })
  179. @ClsiManager.sendExternalRequest @submission_id, @clsi_request, {}, @callback
  180. it "should call the callback only once", ->
  181. @callback.calledOnce.should.equal true
  182. it "should call the callback with an error", ->
  183. @callback.calledWithExactly(new Error("failed")).should.equal true
  184. describe "deleteAuxFiles", ->
  185. beforeEach ->
  186. @ClsiManager._makeRequest = sinon.stub().callsArg(2)
  187. @DocumentUpdaterHandler.clearProjectState = sinon.stub().callsArg(1)
  188. describe "with the standard compileGroup", ->
  189. beforeEach ->
  190. @ClsiManager.deleteAuxFiles @project_id, @user_id, {compileGroup: "standard"}, @callback
  191. it "should call the delete method in the standard CLSI", ->
  192. @ClsiManager._makeRequest
  193. .calledWith(@project_id, { method:"DELETE", url:"#{@settings.apis.clsi.url}/project/#{@project_id}/user/#{@user_id}"})
  194. .should.equal true
  195. it "should clear the project state from the docupdater", ->
  196. @DocumentUpdaterHandler.clearProjectState
  197. .calledWith(@project_id)
  198. .should.equal true
  199. it "should call the callback", ->
  200. @callback.called.should.equal true
  201. describe "_buildRequest", ->
  202. beforeEach ->
  203. @project =
  204. _id: @project_id
  205. compiler: @compiler = "latex"
  206. rootDoc_id: "mock-doc-id-1"
  207. imageName: @image = "mock-image-name"
  208. @docs = {
  209. "/main.tex": @doc_1 = {
  210. name: "main.tex"
  211. _id: "mock-doc-id-1"
  212. lines: ["Hello", "world"]
  213. },
  214. "/chapters/chapter1.tex": @doc_2 = {
  215. name: "chapter1.tex"
  216. _id: "mock-doc-id-2"
  217. lines: [
  218. "Chapter 1"
  219. ]
  220. }
  221. }
  222. @files = {
  223. "/images/image.png": @file_1 = {
  224. name: "image.png"
  225. _id: "mock-file-id-1"
  226. created: new Date()
  227. }
  228. }
  229. @Project.findById = sinon.stub().callsArgWith(2, null, @project)
  230. @ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @docs)
  231. @ProjectEntityHandler.getAllFiles = sinon.stub().callsArgWith(1, null, @files)
  232. @ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, @project)
  233. @DocumentUpdaterHandler.flushProjectToMongo = sinon.stub().callsArgWith(1, null)
  234. describe "with a valid project", ->
  235. beforeEach (done) ->
  236. @ClsiManager._buildRequest @project_id, {timeout:100}, (error, request) =>
  237. @request = request
  238. done()
  239. it "should get the project with the required fields", ->
  240. @ProjectGetter.getProject
  241. .calledWith(@project_id, {compiler:1, rootDoc_id: 1, imageName: 1, rootFolder: 1})
  242. .should.equal true
  243. it "should flush the project to the database", ->
  244. @DocumentUpdaterHandler.flushProjectToMongo
  245. .calledWith(@project_id)
  246. .should.equal true
  247. it "should get all the docs", ->
  248. @ProjectEntityHandler.getAllDocs
  249. .calledWith(@project_id)
  250. .should.equal true
  251. it "should get all the files", ->
  252. @ProjectEntityHandler.getAllFiles
  253. .calledWith(@project_id)
  254. .should.equal true
  255. it "should build up the CLSI request", ->
  256. expect(@request).to.deep.equal(
  257. compile:
  258. options:
  259. compiler: @compiler
  260. timeout : 100
  261. imageName: @image
  262. draft: false
  263. check: undefined
  264. syncType: undefined # "full"
  265. syncState: undefined # "01234567890abcdef"
  266. rootResourcePath: "main.tex"
  267. resources: [{
  268. path: "main.tex"
  269. content: @doc_1.lines.join("\n")
  270. }, {
  271. path: "chapters/chapter1.tex"
  272. content: @doc_2.lines.join("\n")
  273. }, {
  274. path: "images/image.png"
  275. url: "#{@settings.apis.filestore.url}/project/#{@project_id}/file/#{@file_1._id}"
  276. modified: @file_1.created.getTime()
  277. }]
  278. )
  279. describe "with the incremental compile option", ->
  280. beforeEach (done) ->
  281. @ClsiStateManager.computeHash = sinon.stub().callsArgWith(2, null, @project_state_hash = "01234567890abcdef")
  282. @DocumentUpdaterHandler.getProjectDocsIfMatch = sinon.stub().callsArgWith(2, null, [{_id:@doc_1._id, lines: @doc_1.lines, v: 123}])
  283. @ProjectEntityHandler.getAllDocPathsFromProject = sinon.stub().callsArgWith(1, null, {"mock-doc-id-1":"main.tex"})
  284. @ClsiManager._buildRequest @project_id, {timeout:100, incrementalCompilesEnabled:true}, (error, request) =>
  285. @request = request
  286. done()
  287. it "should get the project with the required fields", ->
  288. @ProjectGetter.getProject
  289. .calledWith(@project_id, {compiler:1, rootDoc_id: 1, imageName: 1, rootFolder: 1})
  290. .should.equal true
  291. it "should not explicitly flush the project to the database", ->
  292. @DocumentUpdaterHandler.flushProjectToMongo
  293. .calledWith(@project_id)
  294. .should.equal false
  295. it "should get only the live docs from the docupdater with a background flush in docupdater", ->
  296. @DocumentUpdaterHandler.getProjectDocsIfMatch
  297. .calledWith(@project_id)
  298. .should.equal true
  299. it "should not get any of the files", ->
  300. @ProjectEntityHandler.getAllFiles
  301. .called.should.equal false
  302. it "should build up the CLSI request", ->
  303. expect(@request).to.deep.equal(
  304. compile:
  305. options:
  306. compiler: @compiler
  307. timeout : 100
  308. imageName: @image
  309. draft: false
  310. check: undefined
  311. syncType: "incremental"
  312. syncState: "01234567890abcdef"
  313. rootResourcePath: "main.tex"
  314. resources: [{
  315. path: "main.tex"
  316. content: @doc_1.lines.join("\n")
  317. }]
  318. )
  319. describe "when the root doc is set and not in the docupdater", ->
  320. beforeEach (done) ->
  321. @ClsiStateManager.computeHash = sinon.stub().callsArgWith(2, null, @project_state_hash = "01234567890abcdef")
  322. @DocumentUpdaterHandler.getProjectDocsIfMatch = sinon.stub().callsArgWith(2, null, [{_id:@doc_1._id, lines: @doc_1.lines, v: 123}])
  323. @ProjectEntityHandler.getAllDocPathsFromProject = sinon.stub().callsArgWith(1, null, {"mock-doc-id-1":"main.tex", "mock-doc-id-2":"/chapters/chapter1.tex"})
  324. @ClsiManager._buildRequest @project_id, {timeout:100, incrementalCompilesEnabled:true, rootDoc_id:"mock-doc-id-2"}, (error, request) =>
  325. @request = request
  326. done()
  327. it "should still change the root path", ->
  328. @request.compile.rootResourcePath.should.equal "chapters/chapter1.tex"
  329. describe "when root doc override is valid", ->
  330. beforeEach (done) ->
  331. @ClsiManager._buildRequest @project_id, {rootDoc_id:"mock-doc-id-2"}, (error, request) =>
  332. @request = request
  333. done()
  334. it "should change root path", ->
  335. @request.compile.rootResourcePath.should.equal "chapters/chapter1.tex"
  336. describe "when root doc override is invalid", ->
  337. beforeEach (done) ->
  338. @ClsiManager._buildRequest @project_id, {rootDoc_id:"invalid-id"}, (error, request) =>
  339. @request = request
  340. done()
  341. it "should fallback to default root doc", ->
  342. @request.compile.rootResourcePath.should.equal "main.tex"
  343. describe "when the project has an invalid compiler", ->
  344. beforeEach (done) ->
  345. @project.compiler = "context"
  346. @ClsiManager._buildRequest @project, null, (error, request) =>
  347. @request = request
  348. done()
  349. it "should set the compiler to pdflatex", ->
  350. @request.compile.options.compiler.should.equal "pdflatex"
  351. describe "when there is no valid root document", ->
  352. beforeEach (done) ->
  353. @project.rootDoc_id = "not-valid"
  354. @ClsiManager._buildRequest @project, null, (@error, @request) =>
  355. done()
  356. it "should set to main.tex", ->
  357. @request.compile.rootResourcePath.should.equal "main.tex"
  358. describe "when there is no valid root document and no main.tex document", ->
  359. beforeEach () ->
  360. @project.rootDoc_id = "not-valid"
  361. @docs = {
  362. "/other.tex": @doc_1 = {
  363. name: "other.tex"
  364. _id: "mock-doc-id-1"
  365. lines: ["Hello", "world"]
  366. },
  367. "/chapters/chapter1.tex": @doc_2 = {
  368. name: "chapter1.tex"
  369. _id: "mock-doc-id-2"
  370. lines: [
  371. "Chapter 1"
  372. ]
  373. }
  374. }
  375. @ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @docs)
  376. @ClsiManager._buildRequest @project, null, @callback
  377. it "should report an error", ->
  378. @callback.calledWith(new Error("no main file specified")).should.equal true
  379. describe "when there is no valid root document and a single document which is not main.tex", ->
  380. beforeEach (done) ->
  381. @project.rootDoc_id = "not-valid"
  382. @docs = {
  383. "/other.tex": @doc_1 = {
  384. name: "other.tex"
  385. _id: "mock-doc-id-1"
  386. lines: ["Hello", "world"]
  387. }
  388. }
  389. @ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @docs)
  390. @ClsiManager._buildRequest @project, null, (@error, @request) =>
  391. done()
  392. it "should set io to the only file", ->
  393. @request.compile.rootResourcePath.should.equal "other.tex"
  394. describe "with the draft option", ->
  395. it "should add the draft option into the request", (done) ->
  396. @ClsiManager._buildRequest @project_id, {timeout:100, draft: true}, (error, request) =>
  397. request.compile.options.draft.should.equal true
  398. done()
  399. describe '_postToClsi', ->
  400. beforeEach ->
  401. @req = { mock: "req" }
  402. describe "successfully", ->
  403. beforeEach ->
  404. @ClsiManager._makeRequest = sinon.stub().callsArgWith(2, null, {statusCode: 204}, @body = { mock: "foo" })
  405. @ClsiManager._postToClsi @project_id, @user_id, @req, "standard", @callback
  406. it 'should send the request to the CLSI', ->
  407. url = "#{@settings.apis.clsi.url}/project/#{@project_id}/user/#{@user_id}/compile"
  408. @ClsiManager._makeRequest.calledWith(@project_id, {
  409. method: "POST",
  410. url: url
  411. json: @req
  412. }).should.equal true
  413. it "should call the callback with the body and no error", ->
  414. @callback.calledWith(null, @body).should.equal true
  415. describe "when the CLSI returns an error", ->
  416. beforeEach ->
  417. @ClsiManager._makeRequest = sinon.stub().callsArgWith(2, null, {statusCode: 500}, @body = { mock: "foo" })
  418. @ClsiManager._postToClsi @project_id, @user_id, @req, "standard", @callback
  419. it "should call the callback with the body and the error", ->
  420. @callback.calledWith(new Error("CLSI returned non-success code: 500"), @body).should.equal true
  421. describe "wordCount", ->
  422. beforeEach ->
  423. @ClsiManager._makeRequest = sinon.stub().callsArgWith(2, null, {statusCode: 200}, @body = { mock: "foo" })
  424. @ClsiManager._buildRequest = sinon.stub().callsArgWith(2, null, @req = { compile: { rootResourcePath: "rootfile.text", options: {} } })
  425. describe "with root file", ->
  426. beforeEach ->
  427. @ClsiManager.wordCount @project_id, @user_id, false, {}, @callback
  428. it "should call wordCount with root file", ->
  429. @ClsiManager._makeRequest
  430. .calledWith(@project_id, {method: "GET", url: "http://clsi.example.com/project/#{@project_id}/user/#{@user_id}/wordcount", qs: {file: "rootfile.text",image:undefined}})
  431. .should.equal true
  432. it "should call the callback", ->
  433. @callback.called.should.equal true
  434. describe "with param file", ->
  435. beforeEach ->
  436. @ClsiManager.wordCount @project_id, @user_id, "main.tex", {}, @callback
  437. it "should call wordCount with param file", ->
  438. @ClsiManager._makeRequest
  439. .calledWith(@project_id, { method: "GET", url: "http://clsi.example.com/project/#{@project_id}/user/#{@user_id}/wordcount", qs:{file:"main.tex",image:undefined}})
  440. .should.equal true
  441. describe "with image", ->
  442. beforeEach ->
  443. @req.compile.options.imageName = @image = "example.com/mock/image"
  444. @ClsiManager.wordCount @project_id, @user_id, "main.tex", {}, @callback
  445. it "should call wordCount with file and image", ->
  446. @ClsiManager._makeRequest
  447. .calledWith(@project_id, { method: "GET", url: "http://clsi.example.com/project/#{@project_id}/user/#{@user_id}/wordcount", qs:{file:"main.tex",image:@image}})
  448. .should.equal true
  449. describe "_makeRequest", ->
  450. beforeEach ->
  451. @response = {there:"something"}
  452. @request.callsArgWith(1, null, @response)
  453. @opts =
  454. method: "SOMETHIGN"
  455. url: "http://a place on the web"
  456. it "should process a request with a cookie jar", (done)->
  457. @ClsiManager._makeRequest @project_id, @opts, =>
  458. args = @request.args[0]
  459. args[0].method.should.equal @opts.method
  460. args[0].url.should.equal @opts.url
  461. args[0].jar.should.equal @jar
  462. done()
  463. it "should set the cookie again on response as it might have changed", (done)->
  464. @ClsiManager._makeRequest @project_id, @opts, =>
  465. @ClsiCookieManager.setServerId.calledWith(@project_id, @response).should.equal true
  466. done()
  467. describe "_makeGoogleCloudRequest", ->
  468. beforeEach ->
  469. @settings.apis.clsi_new =
  470. url : "https://compiles.somewhere.test"
  471. @response = {there:"something"}
  472. @request.callsArgWith(1, null, @response)
  473. @opts =
  474. url: @ClsiManager._getCompilerUrl(null, @project_id)
  475. it "should change the domain on the url", (done)->
  476. @ClsiManager._makeNewBackendRequest @project_id, @opts, =>
  477. args = @request.args[0]
  478. args[0].url.should.equal "https://compiles.somewhere.test/project/#{@project_id}"
  479. done()
  480. it "should not make a request if there is not clsi_new url", (done)->
  481. @settings.apis.clsi_new = undefined
  482. @ClsiManager._makeNewBackendRequest @project_id, @opts, (err)=>
  483. expect(err).to.equal undefined
  484. @request.callCount.should.equal 0
  485. done()