ProjectStructureTests.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  1. const { expect } = require('chai')
  2. const mkdirp = require('mkdirp')
  3. const { ObjectId } = require('mongojs')
  4. const Path = require('path')
  5. const fs = require('fs')
  6. const Settings = require('settings-sharelatex')
  7. const _ = require('underscore')
  8. const ProjectGetter = require('../../../app/src/Features/Project/ProjectGetter.js')
  9. const MockDocUpdaterApi = require('./helpers/MockDocUpdaterApi')
  10. require('./helpers/MockFileStoreApi')
  11. require('./helpers/MockProjectHistoryApi')
  12. const User = require('./helpers/User')
  13. describe('ProjectStructureChanges', function() {
  14. beforeEach(function(done) {
  15. this.owner = new User()
  16. this.owner.login(done)
  17. })
  18. function createExampleProject(test, callback) {
  19. test.owner.createProject(
  20. 'example-project',
  21. { template: 'example' },
  22. (error, projectId) => {
  23. if (error) {
  24. throw error
  25. }
  26. test.exampleProjectId = projectId
  27. ProjectGetter.getProject(test.exampleProjectId, (error, project) => {
  28. if (error) {
  29. throw error
  30. }
  31. test.rootFolderId = project.rootFolder[0]._id.toString()
  32. callback()
  33. })
  34. }
  35. )
  36. }
  37. function createExampleDoc(test, callback) {
  38. ProjectGetter.getProject(test.exampleProjectId, (error, project) => {
  39. if (error) {
  40. throw error
  41. }
  42. test.owner.request.post(
  43. {
  44. uri: `project/${test.exampleProjectId}/doc`,
  45. json: {
  46. name: 'new.tex',
  47. parent_folder_id: project.rootFolder[0]._id
  48. }
  49. },
  50. (error, res, body) => {
  51. if (error) {
  52. throw error
  53. }
  54. if (res.statusCode < 200 || res.statusCode >= 300) {
  55. throw new Error(`failed to add doc ${res.statusCode}`)
  56. }
  57. test.exampleDocId = body._id
  58. callback()
  59. }
  60. )
  61. })
  62. }
  63. function createExampleFolder(test, callback) {
  64. test.owner.request.post(
  65. {
  66. uri: `project/${test.exampleProjectId}/folder`,
  67. json: {
  68. name: 'foo'
  69. }
  70. },
  71. (error, res, body) => {
  72. if (error) {
  73. throw error
  74. }
  75. if (res.statusCode < 200 || res.statusCode >= 300) {
  76. throw new Error(`failed to add doc ${res.statusCode}`)
  77. }
  78. test.exampleFolderId = body._id
  79. callback()
  80. }
  81. )
  82. }
  83. function uploadFile(test, file, name, contentType, callback) {
  84. const imageFile = fs.createReadStream(
  85. Path.resolve(Path.join(__dirname, '..', 'files', file))
  86. )
  87. test.owner.request.post(
  88. {
  89. uri: `project/${test.exampleProjectId}/upload`,
  90. qs: {
  91. folder_id: test.rootFolderId
  92. },
  93. formData: {
  94. qqfile: {
  95. value: imageFile,
  96. options: {
  97. filename: name,
  98. contentType: contentType
  99. }
  100. }
  101. }
  102. },
  103. (error, res, body) => {
  104. if (error) {
  105. throw error
  106. }
  107. if (res.statusCode < 200 || res.statusCode >= 300) {
  108. throw new Error(`failed to upload file ${res.statusCode}`)
  109. }
  110. test.exampleFileId = JSON.parse(body).entity_id
  111. callback()
  112. }
  113. )
  114. }
  115. function uploadExampleFile(test, callback) {
  116. uploadFile(test, '1pixel.png', '1pixel.png', 'image/png', callback)
  117. }
  118. function uploadExampleProject(test, zipFilename, options, callback) {
  119. if (typeof options === 'function') {
  120. callback = options
  121. options = {}
  122. }
  123. const zipFile = fs.createReadStream(
  124. Path.resolve(Path.join(__dirname, '..', 'files', zipFilename))
  125. )
  126. test.owner.request.post(
  127. {
  128. uri: 'project/new/upload',
  129. formData: {
  130. qqfile: zipFile
  131. }
  132. },
  133. (error, res, body) => {
  134. if (error) {
  135. throw error
  136. }
  137. if (
  138. !options.allowBadStatus &&
  139. (res.statusCode < 200 || res.statusCode >= 300)
  140. ) {
  141. throw new Error(`failed to upload project ${res.statusCode}`)
  142. }
  143. test.uploadedProjectId = JSON.parse(body).project_id
  144. test.res = res
  145. callback()
  146. }
  147. )
  148. }
  149. function moveItem(test, type, itemId, folderId, callback) {
  150. test.owner.request.post(
  151. {
  152. uri: `project/${test.exampleProjectId}/${type}/${itemId}/move`,
  153. json: {
  154. folder_id: folderId
  155. }
  156. },
  157. (error, res) => {
  158. if (error) {
  159. throw error
  160. }
  161. if (res.statusCode < 200 || res.statusCode >= 300) {
  162. throw new Error(`failed to move ${type} ${res.statusCode}`)
  163. }
  164. callback()
  165. }
  166. )
  167. }
  168. function renameItem(test, type, itemId, name, callback) {
  169. test.owner.request.post(
  170. {
  171. uri: `project/${test.exampleProjectId}/${type}/${itemId}/rename`,
  172. json: {
  173. name: name
  174. }
  175. },
  176. (error, res) => {
  177. if (error) {
  178. throw error
  179. }
  180. if (res.statusCode < 200 || res.statusCode >= 300) {
  181. throw new Error(`failed to rename ${type} ${res.statusCode}`)
  182. }
  183. callback()
  184. }
  185. )
  186. }
  187. function deleteItem(test, type, itemId, callback) {
  188. test.owner.request.delete(
  189. {
  190. uri: `project/${test.exampleProjectId}/${type}/${itemId}`
  191. },
  192. (error, res) => {
  193. if (error) {
  194. throw error
  195. }
  196. if (res.statusCode < 200 || res.statusCode >= 300) {
  197. throw new Error(`failed to delete folder ${res.statusCode}`)
  198. }
  199. callback()
  200. }
  201. )
  202. }
  203. function verifyVersionIncremented(test, updateVersion, increment, callback) {
  204. expect(updateVersion).to.equal(test.project0.version + increment)
  205. ProjectGetter.getProject(test.exampleProjectId, (error, newProject) => {
  206. if (error) {
  207. throw error
  208. }
  209. expect(newProject.version).to.equal(test.project0.version + increment)
  210. callback()
  211. })
  212. }
  213. describe('creating a project from the example template', function() {
  214. beforeEach(function(done) {
  215. MockDocUpdaterApi.clearProjectStructureUpdates()
  216. createExampleProject(this, done)
  217. })
  218. it('should version creating a doc', function() {
  219. const {
  220. docUpdates: updates,
  221. version
  222. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  223. expect(updates.length).to.equal(2)
  224. _.each(updates, update => {
  225. expect(update.userId).to.equal(this.owner._id)
  226. expect(update.docLines).to.be.a('string')
  227. })
  228. expect(_.where(updates, { pathname: '/main.tex' }).length).to.equal(1)
  229. expect(_.where(updates, { pathname: '/references.bib' }).length).to.equal(
  230. 1
  231. )
  232. expect(version).to.equal(3)
  233. })
  234. it('should version creating a file', function() {
  235. const {
  236. fileUpdates: updates,
  237. version
  238. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  239. expect(updates.length).to.equal(1)
  240. const update = updates[0]
  241. expect(update.userId).to.equal(this.owner._id)
  242. expect(update.pathname).to.equal('/universe.jpg')
  243. expect(update.url).to.be.a('string')
  244. expect(version).to.equal(3)
  245. })
  246. })
  247. describe('duplicating a project', function() {
  248. beforeEach(function(done) {
  249. MockDocUpdaterApi.clearProjectStructureUpdates()
  250. createExampleProject(this, () => {
  251. this.owner.request.post(
  252. {
  253. uri: `/Project/${this.exampleProjectId}/clone`,
  254. json: {
  255. projectName: 'new.tex'
  256. }
  257. },
  258. (error, res, body) => {
  259. if (error) {
  260. throw error
  261. }
  262. if (res.statusCode < 200 || res.statusCode >= 300) {
  263. throw new Error(`failed to clone project ${res.statusCode}`)
  264. }
  265. this.dupProjectId = body.project_id
  266. done()
  267. }
  268. )
  269. })
  270. })
  271. it('should version the docs created', function() {
  272. const {
  273. docUpdates: updates,
  274. version
  275. } = MockDocUpdaterApi.getProjectStructureUpdates(this.dupProjectId)
  276. expect(updates.length).to.equal(2)
  277. _.each(updates, update => {
  278. expect(update.userId).to.equal(this.owner._id)
  279. expect(update.docLines).to.be.a('string')
  280. })
  281. expect(_.where(updates, { pathname: '/main.tex' }).length).to.equal(1)
  282. expect(_.where(updates, { pathname: '/references.bib' }).length).to.equal(
  283. 1
  284. )
  285. expect(version).to.equal(3)
  286. })
  287. it('should version the files created', function() {
  288. const {
  289. fileUpdates: updates,
  290. version
  291. } = MockDocUpdaterApi.getProjectStructureUpdates(this.dupProjectId)
  292. expect(updates.length).to.equal(1)
  293. const update = updates[0]
  294. expect(update.userId).to.equal(this.owner._id)
  295. expect(update.pathname).to.equal('/universe.jpg')
  296. expect(update.url).to.be.a('string')
  297. expect(version).to.equal(3)
  298. })
  299. })
  300. describe('adding a doc', function() {
  301. beforeEach(function(done) {
  302. createExampleProject(this, () => {
  303. MockDocUpdaterApi.clearProjectStructureUpdates()
  304. ProjectGetter.getProject(this.exampleProjectId, (error, project) => {
  305. if (error) {
  306. throw error
  307. }
  308. this.project0 = project
  309. createExampleDoc(this, done)
  310. })
  311. })
  312. })
  313. it('should version the doc added', function(done) {
  314. const {
  315. docUpdates: updates,
  316. version
  317. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  318. expect(updates.length).to.equal(1)
  319. const update = updates[0]
  320. expect(update.userId).to.equal(this.owner._id)
  321. expect(update.pathname).to.equal('/new.tex')
  322. expect(update.docLines).to.be.a('string')
  323. verifyVersionIncremented(this, version, 1, done)
  324. })
  325. })
  326. describe('uploading a project', function() {
  327. beforeEach(function(done) {
  328. uploadExampleProject(this, 'test_project.zip', done)
  329. })
  330. it('should version the docs created', function() {
  331. const {
  332. docUpdates: updates,
  333. version
  334. } = MockDocUpdaterApi.getProjectStructureUpdates(this.uploadedProjectId)
  335. expect(updates.length).to.equal(1)
  336. const update = updates[0]
  337. expect(update.userId).to.equal(this.owner._id)
  338. expect(update.pathname).to.equal('/main.tex')
  339. expect(update.docLines).to.equal('Test')
  340. expect(version).to.equal(2)
  341. })
  342. it('should version the files created', function() {
  343. const {
  344. fileUpdates: updates,
  345. version
  346. } = MockDocUpdaterApi.getProjectStructureUpdates(this.uploadedProjectId)
  347. expect(updates.length).to.equal(1)
  348. const update = updates[0]
  349. expect(update.userId).to.equal(this.owner._id)
  350. expect(update.pathname).to.equal('/1pixel.png')
  351. expect(update.url).to.be.a('string')
  352. expect(version).to.equal(2)
  353. })
  354. })
  355. describe('uploading a project with a name', function() {
  356. beforeEach(function(done) {
  357. this.testProjectName = 'wombat'
  358. uploadExampleProject(this, 'test_project_with_name.zip', done)
  359. })
  360. it('should set the project name from the zip contents', function(done) {
  361. ProjectGetter.getProject(this.uploadedProjectId, (error, project) => {
  362. expect(error).not.to.exist
  363. expect(project.name).to.equal(this.testProjectName)
  364. done()
  365. })
  366. })
  367. })
  368. describe('uploading a project with an invalid name', function() {
  369. beforeEach(function(done) {
  370. this.testProjectMatch = /^bad[^\\]+name$/
  371. uploadExampleProject(this, 'test_project_with_invalid_name.zip', done)
  372. })
  373. it('should set the project name from the zip contents', function(done) {
  374. ProjectGetter.getProject(this.uploadedProjectId, (error, project) => {
  375. expect(error).not.to.exist
  376. expect(project.name).to.match(this.testProjectMatch)
  377. done()
  378. })
  379. })
  380. })
  381. describe('uploading an empty zipfile', function() {
  382. beforeEach(function(done) {
  383. uploadExampleProject(
  384. this,
  385. 'test_project_empty.zip',
  386. { allowBadStatus: true },
  387. done
  388. )
  389. })
  390. it('should fail with 422 error', function() {
  391. expect(this.res.statusCode).to.equal(422)
  392. })
  393. })
  394. describe('uploading a zipfile containing only empty directories', function() {
  395. beforeEach(function(done) {
  396. uploadExampleProject(
  397. this,
  398. 'test_project_with_empty_folder.zip',
  399. { allowBadStatus: true },
  400. done
  401. )
  402. })
  403. it('should fail with 422 error', function() {
  404. expect(this.res.statusCode).to.equal(422)
  405. })
  406. })
  407. describe('uploading a project with a shared top-level folder', function() {
  408. beforeEach(function(done) {
  409. uploadExampleProject(
  410. this,
  411. 'test_project_with_shared_top_level_folder.zip',
  412. done
  413. )
  414. })
  415. it('should not create the top-level folder', function(done) {
  416. ProjectGetter.getProject(this.uploadedProjectId, (error, project) => {
  417. expect(error).not.to.exist
  418. expect(project.rootFolder[0].folders.length).to.equal(0)
  419. expect(project.rootFolder[0].docs.length).to.equal(2)
  420. done()
  421. })
  422. })
  423. })
  424. describe('uploading a project with backslashes in the path names', function() {
  425. beforeEach(function(done) {
  426. uploadExampleProject(
  427. this,
  428. 'test_project_with_backslash_in_filename.zip',
  429. done
  430. )
  431. })
  432. it('should treat the backslash as a directory separator', function(done) {
  433. ProjectGetter.getProject(this.uploadedProjectId, (error, project) => {
  434. expect(error).not.to.exist
  435. expect(project.rootFolder[0].folders[0].name).to.equal('styles')
  436. expect(project.rootFolder[0].folders[0].docs[0].name).to.equal('ao.sty')
  437. done()
  438. })
  439. })
  440. })
  441. describe('uploading a project with files in different encodings', function() {
  442. beforeEach(function(done) {
  443. uploadExampleProject(this, 'charsets/charsets.zip', done)
  444. })
  445. it('should correctly parse windows-1252', function() {
  446. const {
  447. docUpdates: updates
  448. } = MockDocUpdaterApi.getProjectStructureUpdates(this.uploadedProjectId)
  449. const update = _.find(
  450. updates,
  451. update => update.pathname === '/test-german-windows-1252.tex'
  452. )
  453. expect(update.docLines).to.contain(
  454. 'Der schnelle braune Fuchs sprang träge über den Hund.'
  455. )
  456. })
  457. it('should correctly parse German utf8', function() {
  458. const {
  459. docUpdates: updates
  460. } = MockDocUpdaterApi.getProjectStructureUpdates(this.uploadedProjectId)
  461. const update = _.find(
  462. updates,
  463. update => update.pathname === '/test-german-utf8x.tex'
  464. )
  465. expect(update.docLines).to.contain(
  466. 'Der schnelle braune Fuchs sprang träge über den Hund.'
  467. )
  468. })
  469. it('should correctly parse little-endian utf16', function() {
  470. const {
  471. docUpdates: updates
  472. } = MockDocUpdaterApi.getProjectStructureUpdates(this.uploadedProjectId)
  473. const update = _.find(
  474. updates,
  475. update => update.pathname === '/test-greek-utf16-le-bom.tex'
  476. )
  477. expect(update.docLines).to.contain(
  478. 'Η γρήγορη καστανή αλεπού πήδηξε χαλαρά πάνω από το σκυλί.'
  479. )
  480. })
  481. it('should correctly parse Greek utf8', function() {
  482. const {
  483. docUpdates: updates
  484. } = MockDocUpdaterApi.getProjectStructureUpdates(this.uploadedProjectId)
  485. const update = _.find(
  486. updates,
  487. update => update.pathname === '/test-greek-utf8x.tex'
  488. )
  489. expect(update.docLines).to.contain(
  490. 'Η γρήγορη καστανή αλεπού πήδηξε χαλαρά πάνω από το σκυλί.'
  491. )
  492. })
  493. })
  494. describe('uploading a file', function() {
  495. beforeEach(function(done) {
  496. createExampleProject(this, () => {
  497. MockDocUpdaterApi.clearProjectStructureUpdates()
  498. ProjectGetter.getProject(this.exampleProjectId, (error, project) => {
  499. if (error) {
  500. throw error
  501. }
  502. this.project0 = project
  503. uploadExampleFile(this, done)
  504. })
  505. })
  506. })
  507. it('should version a newly uploaded file', function(done) {
  508. const {
  509. fileUpdates: updates,
  510. version
  511. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  512. expect(updates.length).to.equal(1)
  513. const update = updates[0]
  514. expect(update.userId).to.equal(this.owner._id)
  515. expect(update.pathname).to.equal('/1pixel.png')
  516. expect(update.url).to.be.a('string')
  517. // one file upload
  518. verifyVersionIncremented(this, version, 1, done)
  519. })
  520. it('should version a replacement file', function(done) {
  521. MockDocUpdaterApi.clearProjectStructureUpdates()
  522. uploadFile(this, '2pixel.png', '1pixel.png', 'image/png', () => {
  523. const {
  524. fileUpdates: updates,
  525. version
  526. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  527. expect(updates.length).to.equal(2)
  528. let update = updates[0]
  529. expect(update.userId).to.equal(this.owner._id)
  530. expect(update.pathname).to.equal('/1pixel.png')
  531. update = updates[1]
  532. expect(update.userId).to.equal(this.owner._id)
  533. expect(update.pathname).to.equal('/1pixel.png')
  534. expect(update.url).to.be.a('string')
  535. // two file uploads
  536. verifyVersionIncremented(this, version, 2, done)
  537. })
  538. })
  539. })
  540. describe('moving entities', function() {
  541. beforeEach(function(done) {
  542. createExampleProject(this, () => {
  543. createExampleDoc(this, () => {
  544. uploadExampleFile(this, () => {
  545. createExampleFolder(this, () => {
  546. ProjectGetter.getProject(
  547. this.exampleProjectId,
  548. (error, project) => {
  549. if (error) {
  550. throw error
  551. }
  552. this.project0 = project
  553. MockDocUpdaterApi.clearProjectStructureUpdates()
  554. done()
  555. }
  556. )
  557. })
  558. })
  559. })
  560. })
  561. })
  562. it('should version moving a doc', function(done) {
  563. moveItem(this, 'doc', this.exampleDocId, this.exampleFolderId, () => {
  564. const {
  565. docUpdates: updates,
  566. version
  567. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  568. expect(updates.length).to.equal(1)
  569. const update = updates[0]
  570. expect(update.userId).to.equal(this.owner._id)
  571. expect(update.pathname).to.equal('/new.tex')
  572. expect(update.newPathname).to.equal('/foo/new.tex')
  573. // 2, because it's a delete and then add
  574. verifyVersionIncremented(this, version, 2, done)
  575. })
  576. })
  577. it('should version moving a file', function(done) {
  578. moveItem(this, 'file', this.exampleFileId, this.exampleFolderId, () => {
  579. const {
  580. fileUpdates: updates,
  581. version
  582. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  583. expect(updates.length).to.equal(1)
  584. const update = updates[0]
  585. expect(update.userId).to.equal(this.owner._id)
  586. expect(update.pathname).to.equal('/1pixel.png')
  587. expect(update.newPathname).to.equal('/foo/1pixel.png')
  588. // 2, because it's a delete and then add
  589. verifyVersionIncremented(this, version, 2, done)
  590. })
  591. })
  592. it('should version moving a folder', function(done) {
  593. moveItem(this, 'doc', this.exampleDocId, this.exampleFolderId, () => {
  594. MockDocUpdaterApi.clearProjectStructureUpdates()
  595. this.owner.request.post(
  596. {
  597. uri: `project/${this.exampleProjectId}/folder`,
  598. json: {
  599. name: 'bar'
  600. }
  601. },
  602. (error, res, body) => {
  603. if (error) {
  604. throw error
  605. }
  606. const newFolderId = body._id
  607. moveItem(this, 'folder', this.exampleFolderId, newFolderId, () => {
  608. const {
  609. docUpdates: updates,
  610. version
  611. } = MockDocUpdaterApi.getProjectStructureUpdates(
  612. this.exampleProjectId
  613. )
  614. expect(updates.length).to.equal(1)
  615. let update = updates[0]
  616. expect(update.userId).to.equal(this.owner._id)
  617. expect(update.pathname).to.equal('/foo/new.tex')
  618. expect(update.newPathname).to.equal('/bar/foo/new.tex')
  619. // 5, because it's two file moves plus a folder
  620. verifyVersionIncremented(this, version, 5, done)
  621. })
  622. }
  623. )
  624. })
  625. })
  626. })
  627. describe('renaming entities', function() {
  628. beforeEach(function(done) {
  629. createExampleProject(this, () => {
  630. createExampleDoc(this, () => {
  631. uploadExampleFile(this, () => {
  632. createExampleFolder(this, () => {
  633. moveItem(
  634. this,
  635. 'doc',
  636. this.exampleDocId,
  637. this.exampleFolderId,
  638. () => {
  639. moveItem(
  640. this,
  641. 'file',
  642. this.exampleFileId,
  643. this.exampleFolderId,
  644. () => {
  645. MockDocUpdaterApi.clearProjectStructureUpdates()
  646. ProjectGetter.getProject(
  647. this.exampleProjectId,
  648. (error, project) => {
  649. if (error) {
  650. throw error
  651. }
  652. this.project0 = project
  653. done()
  654. }
  655. )
  656. }
  657. )
  658. }
  659. )
  660. })
  661. })
  662. })
  663. })
  664. })
  665. it('should version renaming a doc', function(done) {
  666. renameItem(this, 'Doc', this.exampleDocId, 'wombat.tex', () => {
  667. const {
  668. docUpdates: updates,
  669. version
  670. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  671. expect(updates.length).to.equal(1)
  672. const update = updates[0]
  673. expect(update.userId).to.equal(this.owner._id)
  674. expect(update.pathname).to.equal('/foo/new.tex')
  675. expect(update.newPathname).to.equal('/foo/wombat.tex')
  676. verifyVersionIncremented(this, version, 1, done)
  677. })
  678. })
  679. it('should version renaming a file', function(done) {
  680. renameItem(this, 'file', this.exampleFileId, 'potato.png', () => {
  681. const {
  682. fileUpdates: updates,
  683. version
  684. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  685. expect(updates.length).to.equal(1)
  686. const update = updates[0]
  687. expect(update.userId).to.equal(this.owner._id)
  688. expect(update.pathname).to.equal('/foo/1pixel.png')
  689. expect(update.newPathname).to.equal('/foo/potato.png')
  690. verifyVersionIncremented(this, version, 1, done)
  691. })
  692. })
  693. it('should version renaming a folder', function(done) {
  694. renameItem(this, 'folder', this.exampleFolderId, 'giraffe', () => {
  695. const {
  696. docUpdates,
  697. fileUpdates,
  698. version
  699. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  700. expect(docUpdates.length).to.equal(1)
  701. const docUpdate = docUpdates[0]
  702. expect(docUpdate.userId).to.equal(this.owner._id)
  703. expect(docUpdate.pathname).to.equal('/foo/new.tex')
  704. expect(docUpdate.newPathname).to.equal('/giraffe/new.tex')
  705. expect(fileUpdates.length).to.equal(1)
  706. const fileUpdate = fileUpdates[0]
  707. expect(fileUpdate.userId).to.equal(this.owner._id)
  708. expect(fileUpdate.pathname).to.equal('/foo/1pixel.png')
  709. expect(fileUpdate.newPathname).to.equal('/giraffe/1pixel.png')
  710. verifyVersionIncremented(this, version, 1, done)
  711. })
  712. })
  713. })
  714. describe('deleting entities', function() {
  715. beforeEach(function(done) {
  716. createExampleProject(this, () => {
  717. createExampleFolder(this, () => {
  718. createExampleDoc(this, () => {
  719. uploadExampleFile(this, () => {
  720. moveItem(
  721. this,
  722. 'doc',
  723. this.exampleDocId,
  724. this.exampleFolderId,
  725. () => {
  726. moveItem(
  727. this,
  728. 'file',
  729. this.exampleFileId,
  730. this.exampleFolderId,
  731. () => {
  732. MockDocUpdaterApi.clearProjectStructureUpdates()
  733. ProjectGetter.getProject(
  734. this.exampleProjectId,
  735. (error, project) => {
  736. if (error) {
  737. throw error
  738. }
  739. this.project0 = project
  740. done()
  741. }
  742. )
  743. }
  744. )
  745. }
  746. )
  747. })
  748. })
  749. })
  750. })
  751. })
  752. it('should version deleting a folder', function(done) {
  753. deleteItem(this, 'folder', this.exampleFolderId, () => {
  754. const {
  755. docUpdates,
  756. fileUpdates,
  757. version
  758. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  759. expect(docUpdates.length).to.equal(1)
  760. const docUpdate = docUpdates[0]
  761. expect(docUpdate.userId).to.equal(this.owner._id)
  762. expect(docUpdate.pathname).to.equal('/foo/new.tex')
  763. expect(docUpdate.newPathname).to.equal('')
  764. expect(fileUpdates.length).to.equal(1)
  765. const fileUpdate = fileUpdates[0]
  766. expect(fileUpdate.userId).to.equal(this.owner._id)
  767. expect(fileUpdate.pathname).to.equal('/foo/1pixel.png')
  768. expect(fileUpdate.newPathname).to.equal('')
  769. verifyVersionIncremented(this, version, 1, done)
  770. })
  771. })
  772. })
  773. describe('tpds', function() {
  774. beforeEach(function(done) {
  775. this.tpdsProjectName = `tpds-project-${new ObjectId().toString()}`
  776. this.owner.createProject(this.tpdsProjectName, (error, projectId) => {
  777. if (error) {
  778. throw error
  779. }
  780. this.exampleProjectId = projectId
  781. mkdirp(Settings.path.dumpFolder, () => {
  782. ProjectGetter.getProject(this.exampleProjectId, (error, project) => {
  783. if (error) {
  784. throw error
  785. }
  786. MockDocUpdaterApi.clearProjectStructureUpdates()
  787. this.rootFolderId = project.rootFolder[0]._id.toString()
  788. this.project0 = project
  789. done()
  790. })
  791. })
  792. })
  793. })
  794. it('should version adding a doc', function(done) {
  795. const texFile = fs.createReadStream(
  796. Path.resolve(Path.join(__dirname, '..', 'files', 'test.tex'))
  797. )
  798. const req = this.owner.request.post({
  799. uri: `/user/${this.owner._id}/update/${this.tpdsProjectName}/test.tex`,
  800. auth: {
  801. user: _.keys(Settings.httpAuthUsers)[0],
  802. pass: _.values(Settings.httpAuthUsers)[0],
  803. sendImmediately: true
  804. }
  805. })
  806. texFile.on('error', err => {
  807. throw err
  808. })
  809. req.on('error', err => {
  810. throw err
  811. })
  812. req.on('response', res => {
  813. if (res.statusCode < 200 || res.statusCode >= 300) {
  814. throw new Error(`failed to upload file ${res.statusCode}`)
  815. }
  816. const {
  817. docUpdates: updates,
  818. version
  819. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  820. expect(updates.length).to.equal(1)
  821. const update = updates[0]
  822. expect(update.userId).to.equal(this.owner._id)
  823. expect(update.pathname).to.equal('/test.tex')
  824. expect(update.docLines).to.equal('Test')
  825. verifyVersionIncremented(this, version, 1, done)
  826. })
  827. texFile.pipe(req)
  828. })
  829. it('should version adding a new file', function(done) {
  830. const imageFile = fs.createReadStream(
  831. Path.resolve(Path.join(__dirname, '..', 'files', '1pixel.png'))
  832. )
  833. const req = this.owner.request.post({
  834. uri: `/user/${this.owner._id}/update/${
  835. this.tpdsProjectName
  836. }/1pixel.png`,
  837. auth: {
  838. user: _.keys(Settings.httpAuthUsers)[0],
  839. pass: _.values(Settings.httpAuthUsers)[0],
  840. sendImmediately: true
  841. }
  842. })
  843. imageFile.on('error', err => {
  844. throw err
  845. })
  846. req.on('error', err => {
  847. throw err
  848. })
  849. req.on('response', res => {
  850. if (res.statusCode < 200 || res.statusCode >= 300) {
  851. throw new Error(`failed to upload file ${res.statusCode}`)
  852. }
  853. const {
  854. fileUpdates: updates,
  855. version
  856. } = MockDocUpdaterApi.getProjectStructureUpdates(this.exampleProjectId)
  857. expect(updates.length).to.equal(1)
  858. const update = updates[0]
  859. expect(update.userId).to.equal(this.owner._id)
  860. expect(update.pathname).to.equal('/1pixel.png')
  861. expect(update.url).to.be.a('string')
  862. verifyVersionIncremented(this, version, 1, done)
  863. })
  864. imageFile.pipe(req)
  865. })
  866. describe('when there are files in the project', function() {
  867. beforeEach(function(done) {
  868. uploadExampleFile(this, () => {
  869. createExampleDoc(this, () => {
  870. ProjectGetter.getProject(
  871. this.exampleProjectId,
  872. (error, project) => {
  873. if (error) {
  874. throw error
  875. }
  876. MockDocUpdaterApi.clearProjectStructureUpdates()
  877. this.project0 = project
  878. done()
  879. }
  880. )
  881. })
  882. })
  883. })
  884. it('should version replacing a file', function(done) {
  885. const imageFile = fs.createReadStream(
  886. Path.resolve(Path.join(__dirname, '..', 'files', '2pixel.png'))
  887. )
  888. const req = this.owner.request.post({
  889. uri: `/user/${this.owner._id}/update/${
  890. this.tpdsProjectName
  891. }/1pixel.png`,
  892. auth: {
  893. user: _.keys(Settings.httpAuthUsers)[0],
  894. pass: _.values(Settings.httpAuthUsers)[0],
  895. sendImmediately: true
  896. }
  897. })
  898. imageFile.on('error', err => {
  899. throw err
  900. })
  901. req.on('error', err => {
  902. throw err
  903. })
  904. req.on('response', res => {
  905. if (res.statusCode < 200 || res.statusCode >= 300) {
  906. throw new Error(`failed to upload file ${res.statusCode}`)
  907. }
  908. const {
  909. fileUpdates: updates,
  910. version
  911. } = MockDocUpdaterApi.getProjectStructureUpdates(
  912. this.exampleProjectId
  913. )
  914. expect(updates.length).to.equal(2)
  915. let update = updates[0]
  916. expect(update.userId).to.equal(this.owner._id)
  917. expect(update.pathname).to.equal('/1pixel.png')
  918. // expect(update.url).to.be.a('string');
  919. update = updates[1]
  920. expect(update.userId).to.equal(this.owner._id)
  921. expect(update.pathname).to.equal('/1pixel.png')
  922. expect(update.url).to.be.a('string')
  923. verifyVersionIncremented(this, version, 1, done)
  924. })
  925. imageFile.pipe(req)
  926. })
  927. it('should version deleting a doc', function(done) {
  928. this.owner.request.delete(
  929. {
  930. uri: `/user/${this.owner._id}/update/${
  931. this.tpdsProjectName
  932. }/new.tex`,
  933. auth: {
  934. user: _.keys(Settings.httpAuthUsers)[0],
  935. pass: _.values(Settings.httpAuthUsers)[0],
  936. sendImmediately: true
  937. }
  938. },
  939. (error, res) => {
  940. if (error) {
  941. throw error
  942. }
  943. if (res.statusCode < 200 || res.statusCode >= 300) {
  944. throw new Error(`failed to delete doc ${res.statusCode}`)
  945. }
  946. const {
  947. docUpdates: updates,
  948. version
  949. } = MockDocUpdaterApi.getProjectStructureUpdates(
  950. this.exampleProjectId
  951. )
  952. expect(updates.length).to.equal(1)
  953. const update = updates[0]
  954. expect(update.userId).to.equal(this.owner._id)
  955. expect(update.pathname).to.equal('/new.tex')
  956. expect(update.newPathname).to.equal('')
  957. verifyVersionIncremented(this, version, 1, done)
  958. }
  959. )
  960. })
  961. })
  962. })
  963. describe('uploading a document', function() {
  964. beforeEach(function(done) {
  965. createExampleProject(this, () => {
  966. MockDocUpdaterApi.clearProjectStructureUpdates()
  967. done()
  968. })
  969. })
  970. describe('with an unusual character set', function() {
  971. it('should correctly handle utf16-le data', function(done) {
  972. uploadFile(
  973. this,
  974. 'charsets/test-greek-utf16-le-bom.tex',
  975. 'test-greek-utf16-le-bom.tex',
  976. 'text/x-tex',
  977. () => {
  978. const {
  979. docUpdates: updates
  980. } = MockDocUpdaterApi.getProjectStructureUpdates(
  981. this.exampleProjectId
  982. )
  983. expect(updates.length).to.equal(1)
  984. const update = updates[0]
  985. expect(update.pathname).to.equal('/test-greek-utf16-le-bom.tex')
  986. expect(update.docLines).to.contain(
  987. 'Η γρήγορη καστανή αλεπού πήδηξε χαλαρά πάνω από το σκυλί.'
  988. )
  989. done()
  990. }
  991. )
  992. })
  993. it('should correctly handle windows1252/iso-8859-1/latin1 data', function(done) {
  994. uploadFile(
  995. this,
  996. 'charsets/test-german-windows-1252.tex',
  997. 'test-german-windows-1252.tex',
  998. 'text/x-tex',
  999. () => {
  1000. const {
  1001. docUpdates: updates
  1002. } = MockDocUpdaterApi.getProjectStructureUpdates(
  1003. this.exampleProjectId
  1004. )
  1005. expect(updates.length).to.equal(1)
  1006. const update = updates[0]
  1007. expect(update.pathname).to.equal('/test-german-windows-1252.tex')
  1008. expect(update.docLines).to.contain(
  1009. 'Der schnelle braune Fuchs sprang träge über den Hund.'
  1010. )
  1011. done()
  1012. }
  1013. )
  1014. })
  1015. })
  1016. })
  1017. })