ProjectEditorHandlerTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. const _ = require('lodash')
  2. const { expect } = require('chai')
  3. const modulePath = '../../../../app/src/Features/Project/ProjectEditorHandler'
  4. const SandboxedModule = require('sandboxed-module')
  5. describe('ProjectEditorHandler', function () {
  6. beforeEach(function () {
  7. this.project = {
  8. _id: 'project-id',
  9. owner_ref: 'owner-id',
  10. name: 'Project Name',
  11. rootDoc_id: 'file-id',
  12. publicAccesLevel: 'private',
  13. deletedByExternalDataSource: false,
  14. rootFolder: [
  15. {
  16. _id: 'root-folder-id',
  17. name: '',
  18. docs: [],
  19. fileRefs: [],
  20. folders: [
  21. {
  22. _id: 'sub-folder-id',
  23. name: 'folder',
  24. docs: [
  25. {
  26. _id: 'doc-id',
  27. name: 'main.tex',
  28. lines: (this.lines = ['line 1', 'line 2', 'line 3']),
  29. },
  30. ],
  31. fileRefs: [
  32. {
  33. _id: 'file-id',
  34. name: 'image.png',
  35. created: (this.created = new Date()),
  36. size: 1234,
  37. },
  38. ],
  39. folders: [],
  40. },
  41. ],
  42. },
  43. ],
  44. }
  45. this.ownerMember = {
  46. user: (this.owner = {
  47. _id: 'owner-id',
  48. first_name: 'Owner',
  49. last_name: 'Overleaf',
  50. email: 'owner@overleaf.com',
  51. features: {
  52. compileTimeout: 240,
  53. },
  54. }),
  55. privilegeLevel: 'owner',
  56. }
  57. this.members = [
  58. {
  59. user: {
  60. _id: 'read-only-id',
  61. first_name: 'Read',
  62. last_name: 'Only',
  63. email: 'read-only@overleaf.com',
  64. },
  65. privilegeLevel: 'readOnly',
  66. },
  67. {
  68. user: {
  69. _id: 'read-write-id',
  70. first_name: 'Read',
  71. last_name: 'Write',
  72. email: 'read-write@overleaf.com',
  73. },
  74. privilegeLevel: 'readAndWrite',
  75. },
  76. ]
  77. this.invites = [
  78. {
  79. _id: 'invite_one',
  80. email: 'user-one@example.com',
  81. privileges: 'readOnly',
  82. projectId: this.project._id,
  83. token: 'my-secret-token1',
  84. },
  85. {
  86. _id: 'invite_two',
  87. email: 'user-two@example.com',
  88. privileges: 'readOnly',
  89. projectId: this.project._id,
  90. token: 'my-secret-token2',
  91. },
  92. ]
  93. this.handler = SandboxedModule.require(modulePath)
  94. })
  95. describe('buildProjectModelView', function () {
  96. describe('with owner, members and invites included', function () {
  97. beforeEach(function () {
  98. this.result = this.handler.buildProjectModelView(
  99. this.project,
  100. this.ownerMember,
  101. this.members,
  102. this.invites,
  103. false
  104. )
  105. })
  106. it('should include the id', function () {
  107. expect(this.result._id).to.exist
  108. this.result._id.should.equal('project-id')
  109. })
  110. it('should include the name', function () {
  111. expect(this.result.name).to.exist
  112. this.result.name.should.equal('Project Name')
  113. })
  114. it('should include the root doc id', function () {
  115. expect(this.result.rootDoc_id).to.exist
  116. this.result.rootDoc_id.should.equal('file-id')
  117. })
  118. it('should include the public access level', function () {
  119. expect(this.result.publicAccesLevel).to.exist
  120. this.result.publicAccesLevel.should.equal('private')
  121. })
  122. it('should include the owner', function () {
  123. expect(this.result.owner).to.exist
  124. this.result.owner._id.should.equal('owner-id')
  125. this.result.owner.email.should.equal('owner@overleaf.com')
  126. this.result.owner.first_name.should.equal('Owner')
  127. this.result.owner.last_name.should.equal('Overleaf')
  128. this.result.owner.privileges.should.equal('owner')
  129. })
  130. it('should gather readOnly_refs and collaberators_refs into a list of members', function () {
  131. const findMember = id => {
  132. for (const member of this.result.members) {
  133. if (member._id === id) {
  134. return member
  135. }
  136. }
  137. return null
  138. }
  139. this.result.members.length.should.equal(2)
  140. expect(findMember('read-only-id')).to.exist
  141. findMember('read-only-id').privileges.should.equal('readOnly')
  142. findMember('read-only-id').first_name.should.equal('Read')
  143. findMember('read-only-id').last_name.should.equal('Only')
  144. findMember('read-only-id').email.should.equal('read-only@overleaf.com')
  145. expect(findMember('read-write-id')).to.exist
  146. findMember('read-write-id').privileges.should.equal('readAndWrite')
  147. findMember('read-write-id').first_name.should.equal('Read')
  148. findMember('read-write-id').last_name.should.equal('Write')
  149. findMember('read-write-id').email.should.equal(
  150. 'read-write@overleaf.com'
  151. )
  152. })
  153. it('should include folders in the project', function () {
  154. this.result.rootFolder[0]._id.should.equal('root-folder-id')
  155. this.result.rootFolder[0].name.should.equal('')
  156. this.result.rootFolder[0].folders[0]._id.should.equal('sub-folder-id')
  157. this.result.rootFolder[0].folders[0].name.should.equal('folder')
  158. })
  159. it('should not duplicate folder contents', function () {
  160. this.result.rootFolder[0].docs.length.should.equal(0)
  161. this.result.rootFolder[0].fileRefs.length.should.equal(0)
  162. })
  163. it('should include files in the project', function () {
  164. this.result.rootFolder[0].folders[0].fileRefs[0]._id.should.equal(
  165. 'file-id'
  166. )
  167. this.result.rootFolder[0].folders[0].fileRefs[0].name.should.equal(
  168. 'image.png'
  169. )
  170. this.result.rootFolder[0].folders[0].fileRefs[0].created.should.equal(
  171. this.created
  172. )
  173. expect(this.result.rootFolder[0].folders[0].fileRefs[0].size).not.to
  174. .exist
  175. })
  176. it('should include docs in the project but not the lines', function () {
  177. this.result.rootFolder[0].folders[0].docs[0]._id.should.equal('doc-id')
  178. this.result.rootFolder[0].folders[0].docs[0].name.should.equal(
  179. 'main.tex'
  180. )
  181. expect(this.result.rootFolder[0].folders[0].docs[0].lines).not.to.exist
  182. })
  183. it('should include invites', function () {
  184. expect(this.result.invites).to.exist
  185. this.result.invites.should.deep.equal(
  186. this.invites.map(invite =>
  187. _.pick(invite, ['_id', 'email', 'privileges'])
  188. )
  189. )
  190. })
  191. it('invites should not include the token', function () {
  192. for (const invite of this.result.invites) {
  193. expect(invite.token).not.to.exist
  194. }
  195. })
  196. it('should have the correct features', function () {
  197. expect(this.result.features.compileTimeout).to.equal(240)
  198. })
  199. })
  200. describe('with a restricted user', function () {
  201. beforeEach(function () {
  202. this.result = this.handler.buildProjectModelView(
  203. this.project,
  204. this.ownerMember,
  205. [],
  206. [],
  207. true
  208. )
  209. })
  210. it('should include the id', function () {
  211. expect(this.result._id).to.exist
  212. this.result._id.should.equal('project-id')
  213. })
  214. it('should include the name', function () {
  215. expect(this.result.name).to.exist
  216. this.result.name.should.equal('Project Name')
  217. })
  218. it('should include the root doc id', function () {
  219. expect(this.result.rootDoc_id).to.exist
  220. this.result.rootDoc_id.should.equal('file-id')
  221. })
  222. it('should include the public access level', function () {
  223. expect(this.result.publicAccesLevel).to.exist
  224. this.result.publicAccesLevel.should.equal('private')
  225. })
  226. it('should hide the owner', function () {
  227. expect(this.result.owner).to.deep.equal({ _id: 'owner-id' })
  228. })
  229. it('should hide members', function () {
  230. this.result.members.length.should.equal(0)
  231. })
  232. it('should include folders in the project', function () {
  233. this.result.rootFolder[0]._id.should.equal('root-folder-id')
  234. this.result.rootFolder[0].name.should.equal('')
  235. this.result.rootFolder[0].folders[0]._id.should.equal('sub-folder-id')
  236. this.result.rootFolder[0].folders[0].name.should.equal('folder')
  237. })
  238. it('should not duplicate folder contents', function () {
  239. this.result.rootFolder[0].docs.length.should.equal(0)
  240. this.result.rootFolder[0].fileRefs.length.should.equal(0)
  241. })
  242. it('should include files in the project', function () {
  243. this.result.rootFolder[0].folders[0].fileRefs[0]._id.should.equal(
  244. 'file-id'
  245. )
  246. this.result.rootFolder[0].folders[0].fileRefs[0].name.should.equal(
  247. 'image.png'
  248. )
  249. this.result.rootFolder[0].folders[0].fileRefs[0].created.should.equal(
  250. this.created
  251. )
  252. expect(this.result.rootFolder[0].folders[0].fileRefs[0].size).not.to
  253. .exist
  254. })
  255. it('should include docs in the project but not the lines', function () {
  256. this.result.rootFolder[0].folders[0].docs[0]._id.should.equal('doc-id')
  257. this.result.rootFolder[0].folders[0].docs[0].name.should.equal(
  258. 'main.tex'
  259. )
  260. expect(this.result.rootFolder[0].folders[0].docs[0].lines).not.to.exist
  261. })
  262. it('should hide invites', function () {
  263. expect(this.result.invites).to.have.length(0)
  264. })
  265. it('should have the correct features', function () {
  266. expect(this.result.features.compileTimeout).to.equal(240)
  267. })
  268. })
  269. describe('deletedByExternalDataSource', function () {
  270. it('should set the deletedByExternalDataSource flag to false when it is not there', function () {
  271. delete this.project.deletedByExternalDataSource
  272. const result = this.handler.buildProjectModelView(
  273. this.project,
  274. this.ownerMember,
  275. this.members,
  276. [],
  277. false
  278. )
  279. result.deletedByExternalDataSource.should.equal(false)
  280. })
  281. it('should set the deletedByExternalDataSource flag to false when it is false', function () {
  282. const result = this.handler.buildProjectModelView(
  283. this.project,
  284. this.ownerMember,
  285. this.members,
  286. [],
  287. false
  288. )
  289. result.deletedByExternalDataSource.should.equal(false)
  290. })
  291. it('should set the deletedByExternalDataSource flag to true when it is true', function () {
  292. this.project.deletedByExternalDataSource = true
  293. const result = this.handler.buildProjectModelView(
  294. this.project,
  295. this.ownerMember,
  296. this.members,
  297. [],
  298. false
  299. )
  300. result.deletedByExternalDataSource.should.equal(true)
  301. })
  302. })
  303. describe('features', function () {
  304. beforeEach(function () {
  305. this.owner.features = {
  306. versioning: true,
  307. collaborators: 3,
  308. compileGroup: 'priority',
  309. compileTimeout: 96,
  310. }
  311. this.result = this.handler.buildProjectModelView(
  312. this.project,
  313. this.ownerMember,
  314. this.members,
  315. [],
  316. false
  317. )
  318. })
  319. it('should copy the owner features to the project', function () {
  320. this.result.features.versioning.should.equal(
  321. this.owner.features.versioning
  322. )
  323. this.result.features.collaborators.should.equal(
  324. this.owner.features.collaborators
  325. )
  326. this.result.features.compileGroup.should.equal(
  327. this.owner.features.compileGroup
  328. )
  329. this.result.features.compileTimeout.should.equal(
  330. this.owner.features.compileTimeout
  331. )
  332. })
  333. })
  334. describe('trackChangesState', function () {
  335. describe('when the owner does not have the trackChanges feature', function () {
  336. beforeEach(function () {
  337. this.owner.features = {
  338. trackChanges: false,
  339. }
  340. this.result = this.handler.buildProjectModelView(
  341. this.project,
  342. this.ownerMember,
  343. this.members,
  344. [],
  345. false
  346. )
  347. })
  348. it('should not emit trackChangesState', function () {
  349. expect(this.result.trackChangesState).to.not.exist
  350. })
  351. })
  352. describe('when the owner has got the trackChanges feature', function () {
  353. beforeEach(function () {
  354. this.owner.features = {
  355. trackChanges: true,
  356. }
  357. })
  358. function genCase([dbEntry, expected]) {
  359. describe(`when track_changes is ${JSON.stringify(
  360. dbEntry
  361. )}`, function () {
  362. beforeEach(function () {
  363. this.project.track_changes = dbEntry
  364. this.result = this.handler.buildProjectModelView(
  365. this.project,
  366. this.ownerMember,
  367. this.members,
  368. [],
  369. false
  370. )
  371. })
  372. it(`should set trackChangesState=${expected}`, function () {
  373. expect(this.result.trackChangesState).to.deep.equal(expected)
  374. })
  375. })
  376. }
  377. const CASES = [
  378. [null, false],
  379. [false, false],
  380. [true, true],
  381. [{ someId: true }, { someId: true }],
  382. ]
  383. CASES.map(genCase)
  384. })
  385. })
  386. })
  387. })