ProjectDetailsHandlerTests.coffee 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. should = require('chai').should()
  2. modulePath = "../../../../app/js/Features/Project/ProjectDetailsHandler"
  3. Errors = require "../../../../app/js/Features/Errors/Errors"
  4. SandboxedModule = require('sandboxed-module')
  5. sinon = require('sinon')
  6. assert = require("chai").assert
  7. expect = require("chai").expect
  8. require('chai').should()
  9. describe 'ProjectDetailsHandler', ->
  10. beforeEach ->
  11. @project_id = "321l3j1kjkjl"
  12. @user_id = "user-id-123"
  13. @project =
  14. name: "project"
  15. description: "this is a great project"
  16. something:"should not exist"
  17. compiler: "latexxxxxx"
  18. owner_ref: @user_id
  19. @user =
  20. features: "mock-features"
  21. @ProjectGetter =
  22. getProjectWithoutDocLines: sinon.stub().callsArgWith(1, null, @project)
  23. getProject: sinon.stub().callsArgWith(2, null, @project)
  24. @ProjectModel =
  25. update: sinon.stub()
  26. findOne: sinon.stub()
  27. @UserGetter =
  28. getUser: sinon.stub().callsArgWith(1, null, @user)
  29. @tpdsUpdateSender =
  30. moveEntity:sinon.stub().callsArgWith 1
  31. @ProjectEntityHandler =
  32. flushProjectToThirdPartyDataStore: sinon.stub().callsArg(1)
  33. @handler = SandboxedModule.require modulePath, requires:
  34. "./ProjectGetter":@ProjectGetter
  35. '../../models/Project': Project:@ProjectModel
  36. "../User/UserGetter": @UserGetter
  37. '../ThirdPartyDataStore/TpdsUpdateSender':@tpdsUpdateSender
  38. "./ProjectEntityHandler": @ProjectEntityHandler
  39. 'logger-sharelatex':
  40. log:->
  41. err:->
  42. './ProjectTokenGenerator': @ProjectTokenGenerator = {}
  43. 'settings-sharelatex': @settings =
  44. defaultFeatures: 'default-features'
  45. describe "getDetails", ->
  46. it "should find the project and owner", (done)->
  47. @handler.getDetails @project_id, (err, details)=>
  48. details.name.should.equal @project.name
  49. details.description.should.equal @project.description
  50. details.compiler.should.equal @project.compiler
  51. details.features.should.equal @user.features
  52. assert.equal(details.something, undefined)
  53. done()
  54. it "should find overleaf metadata if it exists", (done)->
  55. @project.overleaf = { id: 'id' }
  56. @handler.getDetails @project_id, (err, details)=>
  57. details.overleaf.should.equal @project.overleaf
  58. assert.equal(details.something, undefined)
  59. done()
  60. it "should return an error for a non-existent project", (done)->
  61. @ProjectGetter.getProject.callsArg(2, null, null)
  62. err = new Errors.NotFoundError("project not found")
  63. @handler.getDetails "0123456789012345678901234", (error, details) =>
  64. err.should.eql error
  65. done()
  66. it 'should return the default features if no owner found', (done) ->
  67. @UserGetter.getUser.callsArgWith(1, null, null)
  68. @handler.getDetails @project_id, (err, details)=>
  69. details.features.should.equal @settings.defaultFeatures
  70. done()
  71. it "should return the error", (done)->
  72. error = "some error"
  73. @ProjectGetter.getProject.callsArgWith(2, error)
  74. @handler.getDetails @project_id, (err)=>
  75. err.should.equal error
  76. done()
  77. describe "transferOwnership", ->
  78. beforeEach ->
  79. @handler.generateUniqueName = sinon.stub().callsArgWith(2, null, 'teapot')
  80. @ProjectModel.update.callsArgWith(2)
  81. it "should return a not found error if the project can't be found", (done) ->
  82. @ProjectGetter.getProject.callsArgWith(2)
  83. @handler.transferOwnership 'abc', '123', (err) ->
  84. err.should.exist
  85. err.name.should.equal "NotFoundError"
  86. done()
  87. it "should return a not found error if the user can't be found", (done) ->
  88. @ProjectGetter.getProject.callsArgWith(2)
  89. @handler.transferOwnership 'abc', '123', (err) ->
  90. err.should.exist
  91. err.name.should.equal "NotFoundError"
  92. done()
  93. it "should transfer ownership of the project", (done) ->
  94. @handler.transferOwnership 'abc', '123', () =>
  95. sinon.assert.calledWith(@ProjectModel.update, {_id: 'abc'}, sinon.match({$set: {name: 'teapot'}}))
  96. done()
  97. it "should flush the project to tpds", (done) ->
  98. @handler.transferOwnership 'abc', '123', () =>
  99. sinon.assert.calledWith(@ProjectEntityHandler.flushProjectToThirdPartyDataStore, 'abc')
  100. done()
  101. it "should generate a unique name for the project", (done) ->
  102. @handler.transferOwnership 'abc', '123', () =>
  103. sinon.assert.calledWith(@handler.generateUniqueName, '123', @project.name)
  104. done()
  105. it "should append the supplied suffix to the project name, if passed", (done) ->
  106. @handler.transferOwnership 'abc', '123', ' wombat', () =>
  107. sinon.assert.calledWith(@handler.generateUniqueName, '123', "#{@project.name} wombat")
  108. done()
  109. describe "getProjectDescription", ->
  110. it "should make a call to mongo just for the description", (done)->
  111. @ProjectGetter.getProject.callsArgWith(2)
  112. @handler.getProjectDescription @project_id, (err, description)=>
  113. @ProjectGetter.getProject
  114. .calledWith(@project_id, description: true)
  115. .should.equal true
  116. done()
  117. it "should return what the mongo call returns", (done)->
  118. err = "error"
  119. description = "cool project"
  120. @ProjectGetter.getProject.callsArgWith(2, err, {description:description})
  121. @handler.getProjectDescription @project_id, (returnedErr, returnedDescription)=>
  122. err.should.equal returnedErr
  123. description.should.equal returnedDescription
  124. done()
  125. describe "setProjectDescription", ->
  126. beforeEach ->
  127. @description = "updated teh description"
  128. it "should update the project detials", (done)->
  129. @ProjectModel.update.callsArgWith(2)
  130. @handler.setProjectDescription @project_id, @description, =>
  131. @ProjectModel.update.calledWith({_id:@project_id}, {description:@description}).should.equal true
  132. done()
  133. describe "renameProject", ->
  134. beforeEach ->
  135. @handler.validateProjectName = sinon.stub().yields()
  136. @ProjectModel.update.callsArgWith(2)
  137. @newName = "new name here"
  138. it "should update the project with the new name", (done)->
  139. newName = "new name here"
  140. @handler.renameProject @project_id, @newName, =>
  141. @ProjectModel.update.calledWith({_id: @project_id}, {name: @newName}).should.equal true
  142. done()
  143. it "should tell the tpdsUpdateSender", (done)->
  144. @handler.renameProject @project_id, @newName, =>
  145. @tpdsUpdateSender.moveEntity.calledWith({project_id:@project_id, project_name:@project.name, newProjectName:@newName}).should.equal true
  146. done()
  147. it "should not do anything with an invalid name", (done) ->
  148. @handler.validateProjectName = sinon.stub().yields(new Error("invalid name"))
  149. @handler.renameProject @project_id, @newName, =>
  150. @tpdsUpdateSender.moveEntity.called.should.equal false
  151. @ProjectModel.update.called.should.equal false
  152. done()
  153. describe "validateProjectName", ->
  154. it "should reject undefined names", (done) ->
  155. @handler.validateProjectName undefined, (error) ->
  156. expect(error).to.exist
  157. done()
  158. it "should reject empty names", (done) ->
  159. @handler.validateProjectName "", (error) ->
  160. expect(error).to.exist
  161. done()
  162. it "should reject names with /s", (done) ->
  163. @handler.validateProjectName "foo/bar", (error) ->
  164. expect(error).to.exist
  165. done()
  166. it "should reject names with \\s", (done) ->
  167. @handler.validateProjectName "foo\\bar", (error) ->
  168. expect(error).to.exist
  169. done()
  170. it "should reject long names", (done) ->
  171. @handler.validateProjectName new Array(1000).join("a"), (error) ->
  172. expect(error).to.exist
  173. done()
  174. it "should accept normal names", (done) ->
  175. @handler.validateProjectName "foobar", (error) ->
  176. expect(error).to.not.exist
  177. done()
  178. describe "ensureProjectNameIsUnique", ->
  179. beforeEach ->
  180. @result = {
  181. owned: [{_id: 1, name:"name"}, {_id: 2, name: "name1"}, {_id: 3, name: "name11"}, {_id: 100, name: "numeric"}]
  182. readAndWrite: [{_id: 4, name:"name2"}, {_id: 5, name:"name22"}]
  183. readOnly: [{_id:6, name:"name3"}, {_id:7, name: "name33"}]
  184. tokenReadAndWrite: [{_id:8, name:"name4"}, {_id:9, name:"name44"}]
  185. tokenReadOnly: [{_id:10, name:"name5"}, {_id:11, name:"name55"}, {_id:12, name:"x".repeat(15)}]
  186. }
  187. for i in [1..20].concat([30..40])
  188. @result.owned.push {_id: 100 + i, name: "numeric (#{i})"}
  189. @ProjectGetter.findAllUsersProjects = sinon.stub().callsArgWith(2, null, @result)
  190. it "should leave a unique name unchanged", (done) ->
  191. @handler.ensureProjectNameIsUnique @user_id, "unique-name", ["-test-suffix"], (error, name, changed) ->
  192. expect(name).to.equal "unique-name"
  193. expect(changed).to.equal false
  194. done()
  195. it "should append a suffix to an existing name", (done) ->
  196. @handler.ensureProjectNameIsUnique @user_id, "name1", ["-test-suffix"], (error, name, changed) ->
  197. expect(name).to.equal "name1-test-suffix"
  198. expect(changed).to.equal true
  199. done()
  200. it "should fallback to a second suffix when needed", (done) ->
  201. @handler.ensureProjectNameIsUnique @user_id, "name1", ["1", "-test-suffix"], (error, name, changed) ->
  202. expect(name).to.equal "name1-test-suffix"
  203. expect(changed).to.equal true
  204. done()
  205. it "should truncate the name when append a suffix if the result is too long", (done) ->
  206. @handler.MAX_PROJECT_NAME_LENGTH = 20
  207. @handler.ensureProjectNameIsUnique @user_id, "x".repeat(15), ["-test-suffix"], (error, name, changed) ->
  208. expect(name).to.equal "x".repeat(8) + "-test-suffix"
  209. expect(changed).to.equal true
  210. done()
  211. it "should use a numeric index if no suffix is supplied", (done) ->
  212. @handler.ensureProjectNameIsUnique @user_id, "name1", [], (error, name, changed) ->
  213. expect(name).to.equal "name1 (1)"
  214. expect(changed).to.equal true
  215. done()
  216. it "should use a numeric index if all suffixes are exhausted", (done) ->
  217. @handler.ensureProjectNameIsUnique @user_id, "name", ["1", "11"], (error, name, changed) ->
  218. expect(name).to.equal "name (1)"
  219. expect(changed).to.equal true
  220. done()
  221. it "should find the next lowest available numeric index for the base name", (done) ->
  222. @handler.ensureProjectNameIsUnique @user_id, "numeric", [], (error, name, changed) ->
  223. expect(name).to.equal "numeric (21)"
  224. expect(changed).to.equal true
  225. done()
  226. it "should find the next available numeric index when a numeric index is already present", (done) ->
  227. @handler.ensureProjectNameIsUnique @user_id, "numeric (5)", [], (error, name, changed) ->
  228. expect(name).to.equal "numeric (21)"
  229. expect(changed).to.equal true
  230. done()
  231. it "should not find a numeric index lower than the one already present", (done) ->
  232. @handler.ensureProjectNameIsUnique @user_id, "numeric (31)", [], (error, name, changed) ->
  233. expect(name).to.equal "numeric (41)"
  234. expect(changed).to.equal true
  235. done()
  236. describe "fixProjectName", ->
  237. it "should change empty names to Untitled", () ->
  238. expect(@handler.fixProjectName "").to.equal "Untitled"
  239. it "should replace / with -", () ->
  240. expect(@handler.fixProjectName "foo/bar").to.equal "foo-bar"
  241. it "should replace \\ with ''", () ->
  242. expect(@handler.fixProjectName "foo \\ bar").to.equal "foo bar"
  243. it "should truncate long names", () ->
  244. expect(@handler.fixProjectName new Array(1000).join("a")).to.equal "a".repeat(150)
  245. it "should accept normal names", () ->
  246. expect(@handler.fixProjectName "foobar").to.equal "foobar"
  247. describe "setPublicAccessLevel", ->
  248. beforeEach ->
  249. @ProjectModel.update.callsArgWith(2)
  250. @accessLevel = "readOnly"
  251. it "should update the project with the new level", (done)->
  252. @handler.setPublicAccessLevel @project_id, @accessLevel, =>
  253. @ProjectModel.update.calledWith({_id: @project_id}, {publicAccesLevel: @accessLevel}).should.equal true
  254. done()
  255. it 'should not produce an error', (done) ->
  256. @handler.setPublicAccessLevel @project_id, @accessLevel, (err) =>
  257. expect(err).to.not.exist
  258. done()
  259. describe 'when update produces an error', ->
  260. beforeEach ->
  261. @ProjectModel.update.callsArgWith(2, new Error('woops'))
  262. it 'should produce an error', (done) ->
  263. @handler.setPublicAccessLevel @project_id, @accessLevel, (err) =>
  264. expect(err).to.exist
  265. expect(err).to.be.instanceof Error
  266. done()
  267. describe "ensureTokensArePresent", ->
  268. beforeEach ->
  269. describe 'when the project has tokens', ->
  270. beforeEach ->
  271. @project =
  272. _id: @project_id
  273. tokens:
  274. readOnly: 'aaa'
  275. readAndWrite: '42bbb'
  276. readAndWritePrefix: '42'
  277. @ProjectGetter.getProject = sinon.stub()
  278. .callsArgWith(2, null, @project)
  279. @ProjectModel.update = sinon.stub()
  280. it 'should get the project', (done) ->
  281. @handler.ensureTokensArePresent @project_id, (err, tokens) =>
  282. expect(@ProjectGetter.getProject.callCount).to.equal 1
  283. expect(@ProjectGetter.getProject.calledWith(@project_id, {tokens: 1}))
  284. .to.equal true
  285. done()
  286. it 'should not update the project with new tokens', (done) ->
  287. @handler.ensureTokensArePresent @project_id, (err, tokens) =>
  288. expect(@ProjectModel.update.callCount).to.equal 0
  289. done()
  290. it 'should produce the tokens without error', (done) ->
  291. @handler.ensureTokensArePresent @project_id, (err, tokens) =>
  292. expect(err).to.not.exist
  293. expect(tokens).to.deep.equal @project.tokens
  294. done()
  295. describe 'when tokens are missing', ->
  296. beforeEach ->
  297. @project =
  298. _id: @project_id
  299. @ProjectGetter.getProject = sinon.stub()
  300. .callsArgWith(2, null, @project)
  301. @readOnlyToken = 'abc'
  302. @readAndWriteToken = '42def'
  303. @readAndWriteTokenPrefix = '42'
  304. @ProjectTokenGenerator.generateUniqueReadOnlyToken = sinon.stub().callsArgWith(0, null, @readOnlyToken)
  305. @ProjectTokenGenerator.readAndWriteToken = sinon.stub().returns({
  306. token: @readAndWriteToken
  307. numericPrefix: @readAndWriteTokenPrefix
  308. })
  309. @ProjectModel.update = sinon.stub()
  310. .callsArgWith(2, null)
  311. it 'should get the project', (done) ->
  312. @handler.ensureTokensArePresent @project_id, (err, tokens) =>
  313. expect(@ProjectGetter.getProject.callCount).to.equal 1
  314. expect(@ProjectGetter.getProject.calledWith(@project_id, {tokens: 1}))
  315. .to.equal true
  316. done()
  317. it 'should update the project with new tokens', (done) ->
  318. @handler.ensureTokensArePresent @project_id, (err, tokens) =>
  319. expect(@ProjectTokenGenerator.generateUniqueReadOnlyToken.callCount)
  320. .to.equal 1
  321. expect(@ProjectTokenGenerator.readAndWriteToken.callCount)
  322. .to.equal 1
  323. expect(@ProjectModel.update.callCount).to.equal 1
  324. expect(@ProjectModel.update.calledWith(
  325. {_id: @project_id},
  326. {
  327. $set: {
  328. tokens: {
  329. readOnly: @readOnlyToken,
  330. readAndWrite: @readAndWriteToken,
  331. readAndWritePrefix: @readAndWriteTokenPrefix
  332. }
  333. }
  334. }
  335. )).to.equal true
  336. done()
  337. it 'should produce the tokens without error', (done) ->
  338. @handler.ensureTokensArePresent @project_id, (err, tokens) =>
  339. expect(err).to.not.exist
  340. expect(tokens).to.deep.equal {
  341. readOnly: @readOnlyToken,
  342. readAndWrite: @readAndWriteToken,
  343. readAndWritePrefix: @readAndWriteTokenPrefix
  344. }
  345. done()