ProjectStructureTests.js 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461
  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 { Project } = require('../../../app/src/models/Project')
  9. const ProjectGetter = require('../../../app/src/Features/Project/ProjectGetter.js')
  10. const MockDocUpdaterApi = require('./helpers/MockDocUpdaterApi')
  11. require('./helpers/MockFileStoreApi')
  12. require('./helpers/MockProjectHistoryApi')
  13. const User = require('./helpers/User')
  14. describe('ProjectStructureChanges', function() {
  15. let owner
  16. beforeEach(function(done) {
  17. owner = new User()
  18. owner.login(done)
  19. })
  20. function createExampleProject(owner, callback) {
  21. owner.createProject(
  22. 'example-project',
  23. { template: 'example' },
  24. (error, projectId) => {
  25. if (error) {
  26. return callback(error)
  27. }
  28. ProjectGetter.getProject(projectId, (error, project) => {
  29. if (error) {
  30. return callback(error)
  31. }
  32. const rootFolderId = project.rootFolder[0]._id.toString()
  33. callback(null, projectId, rootFolderId)
  34. })
  35. }
  36. )
  37. }
  38. function createExampleDoc(owner, projectId, callback) {
  39. ProjectGetter.getProject(projectId, (error, project) => {
  40. if (error) {
  41. return callback(error)
  42. }
  43. owner.request.post(
  44. {
  45. uri: `project/${projectId}/doc`,
  46. json: {
  47. name: 'new.tex',
  48. parent_folder_id: project.rootFolder[0]._id
  49. }
  50. },
  51. (error, res, body) => {
  52. if (error) {
  53. return callback(error)
  54. }
  55. if (res.statusCode < 200 || res.statusCode >= 300) {
  56. return callback(new Error(`failed to add doc ${res.statusCode}`))
  57. }
  58. callback(null, body._id)
  59. }
  60. )
  61. })
  62. }
  63. function createExampleFolder(owner, projectId, callback) {
  64. owner.request.post(
  65. {
  66. uri: `project/${projectId}/folder`,
  67. json: {
  68. name: 'foo'
  69. }
  70. },
  71. (error, res, body) => {
  72. if (error) {
  73. return callback(error)
  74. }
  75. if (res.statusCode < 200 || res.statusCode >= 300) {
  76. return callback(new Error(`failed to add doc ${res.statusCode}`))
  77. }
  78. callback(null, body._id)
  79. }
  80. )
  81. }
  82. function uploadFile(
  83. owner,
  84. projectId,
  85. folderId,
  86. file,
  87. name,
  88. contentType,
  89. callback
  90. ) {
  91. const imageFile = fs.createReadStream(
  92. Path.resolve(Path.join(__dirname, '..', 'files', file))
  93. )
  94. owner.request.post(
  95. {
  96. uri: `project/${projectId}/upload`,
  97. qs: {
  98. folder_id: folderId
  99. },
  100. formData: {
  101. qqfile: {
  102. value: imageFile,
  103. options: {
  104. filename: name,
  105. contentType: contentType
  106. }
  107. }
  108. }
  109. },
  110. (error, res, body) => {
  111. if (error) {
  112. return callback(error)
  113. }
  114. if (res.statusCode < 200 || res.statusCode >= 300) {
  115. return callback(new Error(`failed to upload file ${res.statusCode}`))
  116. }
  117. callback(null, JSON.parse(body).entity_id)
  118. }
  119. )
  120. }
  121. function uploadExampleFile(owner, projectId, folderId, callback) {
  122. uploadFile(
  123. owner,
  124. projectId,
  125. folderId,
  126. '1pixel.png',
  127. '1pixel.png',
  128. 'image/png',
  129. callback
  130. )
  131. }
  132. function uploadExampleProject(owner, zipFilename, options, callback) {
  133. if (typeof options === 'function') {
  134. callback = options
  135. options = {}
  136. }
  137. const zipFile = fs.createReadStream(
  138. Path.resolve(Path.join(__dirname, '..', 'files', zipFilename))
  139. )
  140. owner.request.post(
  141. {
  142. uri: 'project/new/upload',
  143. formData: {
  144. qqfile: zipFile
  145. }
  146. },
  147. (error, res, body) => {
  148. if (error) {
  149. return callback(error)
  150. }
  151. if (
  152. !options.allowBadStatus &&
  153. (res.statusCode < 200 || res.statusCode >= 300)
  154. ) {
  155. return new Error(`failed to upload project ${res.statusCode}`)
  156. }
  157. callback(null, JSON.parse(body).project_id, res)
  158. }
  159. )
  160. }
  161. function moveItem(owner, projectId, type, itemId, folderId, callback) {
  162. owner.request.post(
  163. {
  164. uri: `project/${projectId}/${type}/${itemId}/move`,
  165. json: {
  166. folder_id: folderId
  167. }
  168. },
  169. (error, res) => {
  170. if (error) {
  171. return callback(error)
  172. }
  173. if (res.statusCode < 200 || res.statusCode >= 300) {
  174. return callback(new Error(`failed to move ${type} ${res.statusCode}`))
  175. }
  176. callback()
  177. }
  178. )
  179. }
  180. function renameItem(owner, projectId, type, itemId, name, callback) {
  181. owner.request.post(
  182. {
  183. uri: `project/${projectId}/${type}/${itemId}/rename`,
  184. json: {
  185. name: name
  186. }
  187. },
  188. (error, res) => {
  189. if (error) {
  190. return callback(error)
  191. }
  192. if (res.statusCode < 200 || res.statusCode >= 300) {
  193. return callback(
  194. new Error(`failed to rename ${type} ${res.statusCode}`)
  195. )
  196. }
  197. callback()
  198. }
  199. )
  200. }
  201. function deleteItem(owner, projectId, type, itemId, callback) {
  202. owner.request.delete(
  203. {
  204. uri: `project/${projectId}/${type}/${itemId}`
  205. },
  206. (error, res) => {
  207. if (error) {
  208. return callback(error)
  209. }
  210. if (res.statusCode < 200 || res.statusCode >= 300) {
  211. return callback(
  212. new Error(`failed to delete folder ${res.statusCode}`)
  213. )
  214. }
  215. callback()
  216. }
  217. )
  218. }
  219. function verifyVersionIncremented(
  220. projectId,
  221. oldVersion,
  222. updateVersion,
  223. increment,
  224. callback
  225. ) {
  226. expect(updateVersion).to.equal(oldVersion + increment)
  227. ProjectGetter.getProject(projectId, (error, newProject) => {
  228. if (error) {
  229. return callback(error)
  230. }
  231. expect(newProject.version).to.equal(updateVersion)
  232. callback()
  233. })
  234. }
  235. describe('creating a project from the example template', function() {
  236. let exampleProjectId
  237. beforeEach(function(done) {
  238. MockDocUpdaterApi.clearProjectStructureUpdates()
  239. createExampleProject(owner, (err, projectId) => {
  240. exampleProjectId = projectId
  241. done(err)
  242. })
  243. })
  244. it('should version creating a doc and a file', function() {
  245. const { updates, version } = MockDocUpdaterApi.getProjectStructureUpdates(
  246. exampleProjectId
  247. )
  248. expect(updates.length).to.equal(3)
  249. for (const update of updates.slice(0, 2)) {
  250. expect(update.type).to.equal('add-doc')
  251. expect(update.userId).to.equal(owner._id)
  252. expect(update.docLines).to.be.a('string')
  253. }
  254. expect(_.where(updates, { pathname: '/main.tex' }).length).to.equal(1)
  255. expect(_.where(updates, { pathname: '/references.bib' }).length).to.equal(
  256. 1
  257. )
  258. expect(updates[2].type).to.equal('add-file')
  259. expect(updates[2].userId).to.equal(owner._id)
  260. expect(updates[2].pathname).to.equal('/universe.jpg')
  261. expect(updates[2].url).to.be.a('string')
  262. expect(version).to.equal(3)
  263. })
  264. })
  265. describe('duplicating a project', function() {
  266. let dupProjectId
  267. beforeEach(function(done) {
  268. MockDocUpdaterApi.clearProjectStructureUpdates()
  269. createExampleProject(owner, (err, projectId) => {
  270. if (err) {
  271. return done(err)
  272. }
  273. owner.request.post(
  274. {
  275. uri: `/Project/${projectId}/clone`,
  276. json: {
  277. projectName: 'new.tex'
  278. }
  279. },
  280. (error, res, body) => {
  281. if (error) {
  282. throw error
  283. }
  284. if (res.statusCode < 200 || res.statusCode >= 300) {
  285. throw new Error(`failed to clone project ${res.statusCode}`)
  286. }
  287. dupProjectId = body.project_id
  288. done()
  289. }
  290. )
  291. })
  292. })
  293. it('should version the docs and files created', function() {
  294. const { updates, version } = MockDocUpdaterApi.getProjectStructureUpdates(
  295. dupProjectId
  296. )
  297. expect(updates.length).to.equal(3)
  298. for (const update of updates.slice(0, 2)) {
  299. expect(update.type).to.equal('add-doc')
  300. expect(update.userId).to.equal(owner._id)
  301. expect(update.docLines).to.be.a('string')
  302. }
  303. expect(_.where(updates, { pathname: '/main.tex' }).length).to.equal(1)
  304. expect(_.where(updates, { pathname: '/references.bib' }).length).to.equal(
  305. 1
  306. )
  307. expect(updates[2].type).to.equal('add-file')
  308. expect(updates[2].userId).to.equal(owner._id)
  309. expect(updates[2].pathname).to.equal('/universe.jpg')
  310. expect(updates[2].url).to.be.a('string')
  311. expect(version).to.equal(1)
  312. })
  313. })
  314. describe('adding a doc', function() {
  315. let exampleProjectId, oldVersion
  316. beforeEach(function(done) {
  317. createExampleProject(owner, (err, projectId) => {
  318. if (err) {
  319. return done(err)
  320. }
  321. exampleProjectId = projectId
  322. MockDocUpdaterApi.clearProjectStructureUpdates()
  323. ProjectGetter.getProject(projectId, (error, project) => {
  324. if (error) {
  325. return done(error)
  326. }
  327. oldVersion = project.version
  328. createExampleDoc(owner, projectId, done)
  329. })
  330. })
  331. })
  332. it('should version the doc added', function(done) {
  333. const {
  334. updates,
  335. version: newVersion
  336. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  337. expect(updates.length).to.equal(1)
  338. const update = updates[0]
  339. expect(update.type).to.equal('add-doc')
  340. expect(update.userId).to.equal(owner._id)
  341. expect(update.pathname).to.equal('/new.tex')
  342. expect(update.docLines).to.be.a('string')
  343. verifyVersionIncremented(
  344. exampleProjectId,
  345. oldVersion,
  346. newVersion,
  347. 1,
  348. done
  349. )
  350. })
  351. })
  352. describe('uploading a project', function() {
  353. let exampleProjectId
  354. beforeEach(function(done) {
  355. uploadExampleProject(owner, 'test_project.zip', (err, projectId) => {
  356. if (err) {
  357. return done(err)
  358. }
  359. exampleProjectId = projectId
  360. done()
  361. })
  362. })
  363. it('should version the docs and files created', function() {
  364. const { updates, version } = MockDocUpdaterApi.getProjectStructureUpdates(
  365. exampleProjectId
  366. )
  367. expect(updates.length).to.equal(2)
  368. expect(updates[0].type).to.equal('add-doc')
  369. expect(updates[0].userId).to.equal(owner._id)
  370. expect(updates[0].pathname).to.equal('/main.tex')
  371. expect(updates[0].docLines).to.equal('Test')
  372. expect(updates[1].type).to.equal('add-file')
  373. expect(updates[1].userId).to.equal(owner._id)
  374. expect(updates[1].pathname).to.equal('/1pixel.png')
  375. expect(updates[1].url).to.be.a('string')
  376. expect(version).to.equal(1)
  377. })
  378. })
  379. describe('uploading a project with a name', function() {
  380. let exampleProjectId
  381. const testProjectName = 'wombat'
  382. beforeEach(function(done) {
  383. uploadExampleProject(
  384. owner,
  385. 'test_project_with_name.zip',
  386. (err, projectId) => {
  387. if (err) {
  388. return done(err)
  389. }
  390. exampleProjectId = projectId
  391. done()
  392. }
  393. )
  394. })
  395. it('should set the project name from the zip contents', function(done) {
  396. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  397. expect(error).not.to.exist
  398. expect(project.name).to.equal(testProjectName)
  399. done()
  400. })
  401. })
  402. })
  403. describe('uploading a project with an invalid name', function() {
  404. let exampleProjectId
  405. const testProjectMatch = /^bad[^\\]+name$/
  406. beforeEach(function(done) {
  407. uploadExampleProject(
  408. owner,
  409. 'test_project_with_invalid_name.zip',
  410. (error, projectId) => {
  411. if (error) {
  412. return done(error)
  413. }
  414. exampleProjectId = projectId
  415. done()
  416. }
  417. )
  418. })
  419. it('should set the project name from the zip contents', function(done) {
  420. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  421. expect(error).not.to.exist
  422. expect(project.name).to.match(testProjectMatch)
  423. done()
  424. })
  425. })
  426. })
  427. describe('uploading an empty zipfile', function() {
  428. let res
  429. beforeEach(function(done) {
  430. uploadExampleProject(
  431. owner,
  432. 'test_project_empty.zip',
  433. { allowBadStatus: true },
  434. (err, projectId, response) => {
  435. if (err) {
  436. return done(err)
  437. }
  438. res = response
  439. done()
  440. }
  441. )
  442. })
  443. it('should fail with 422 error', function() {
  444. expect(res.statusCode).to.equal(422)
  445. })
  446. })
  447. describe('uploading a zipfile containing only empty directories', function() {
  448. let res
  449. beforeEach(function(done) {
  450. uploadExampleProject(
  451. owner,
  452. 'test_project_with_empty_folder.zip',
  453. { allowBadStatus: true },
  454. (err, projectId, response) => {
  455. if (err) {
  456. return done(err)
  457. }
  458. res = response
  459. done()
  460. }
  461. )
  462. })
  463. it('should fail with 422 error', function() {
  464. expect(res.statusCode).to.equal(422)
  465. })
  466. })
  467. describe('uploading a project with a shared top-level folder', function() {
  468. let exampleProjectId
  469. beforeEach(function(done) {
  470. uploadExampleProject(
  471. owner,
  472. 'test_project_with_shared_top_level_folder.zip',
  473. (err, projectId) => {
  474. if (err) {
  475. return done(err)
  476. }
  477. exampleProjectId = projectId
  478. done()
  479. }
  480. )
  481. })
  482. it('should not create the top-level folder', function(done) {
  483. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  484. expect(error).not.to.exist
  485. expect(project.rootFolder[0].folders.length).to.equal(0)
  486. expect(project.rootFolder[0].docs.length).to.equal(2)
  487. done()
  488. })
  489. })
  490. })
  491. describe('uploading a project with backslashes in the path names', function() {
  492. let exampleProjectId
  493. beforeEach(function(done) {
  494. uploadExampleProject(
  495. owner,
  496. 'test_project_with_backslash_in_filename.zip',
  497. (err, projectId) => {
  498. if (err) {
  499. return done(err)
  500. }
  501. exampleProjectId = projectId
  502. done()
  503. }
  504. )
  505. })
  506. it('should treat the backslash as a directory separator', function(done) {
  507. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  508. expect(error).not.to.exist
  509. expect(project.rootFolder[0].folders[0].name).to.equal('styles')
  510. expect(project.rootFolder[0].folders[0].docs[0].name).to.equal('ao.sty')
  511. done()
  512. })
  513. })
  514. })
  515. describe('uploading a project with files in different encodings', function() {
  516. let updates
  517. beforeEach(function(done) {
  518. uploadExampleProject(owner, 'charsets/charsets.zip', (err, projectId) => {
  519. if (err) {
  520. return done(err)
  521. }
  522. updates = MockDocUpdaterApi.getProjectStructureUpdates(projectId)
  523. .updates
  524. done()
  525. })
  526. })
  527. it('should correctly parse windows-1252', function() {
  528. const update = _.find(
  529. updates,
  530. update => update.pathname === '/test-german-windows-1252.tex'
  531. )
  532. expect(update.docLines).to.contain(
  533. 'Der schnelle braune Fuchs sprang träge über den Hund.'
  534. )
  535. })
  536. it('should correctly parse German utf8', function() {
  537. const update = _.find(
  538. updates,
  539. update => update.pathname === '/test-german-utf8x.tex'
  540. )
  541. expect(update.docLines).to.contain(
  542. 'Der schnelle braune Fuchs sprang träge über den Hund.'
  543. )
  544. })
  545. it('should correctly parse little-endian utf16', function() {
  546. const update = _.find(
  547. updates,
  548. update => update.pathname === '/test-greek-utf16-le-bom.tex'
  549. )
  550. expect(update.docLines).to.contain(
  551. 'Η γρήγορη καστανή αλεπού πήδηξε χαλαρά πάνω από το σκυλί.'
  552. )
  553. })
  554. it('should correctly parse Greek utf8', function() {
  555. const update = _.find(
  556. updates,
  557. update => update.pathname === '/test-greek-utf8x.tex'
  558. )
  559. expect(update.docLines).to.contain(
  560. 'Η γρήγορη καστανή αλεπού πήδηξε χαλαρά πάνω από το σκυλί.'
  561. )
  562. })
  563. })
  564. describe('uploading a file', function() {
  565. let exampleProjectId, oldVersion, rootFolderId
  566. beforeEach(function(done) {
  567. createExampleProject(owner, (err, projectId, folderId) => {
  568. if (err) {
  569. return done(err)
  570. }
  571. exampleProjectId = projectId
  572. rootFolderId = folderId
  573. MockDocUpdaterApi.clearProjectStructureUpdates()
  574. ProjectGetter.getProject(projectId, (error, project) => {
  575. if (error) {
  576. throw error
  577. }
  578. oldVersion = project.version
  579. uploadExampleFile(owner, projectId, rootFolderId, done)
  580. })
  581. })
  582. })
  583. it('should version a newly uploaded file', function(done) {
  584. const { updates, version } = MockDocUpdaterApi.getProjectStructureUpdates(
  585. exampleProjectId
  586. )
  587. expect(updates.length).to.equal(1)
  588. const update = updates[0]
  589. expect(update.type).to.equal('add-file')
  590. expect(update.userId).to.equal(owner._id)
  591. expect(update.pathname).to.equal('/1pixel.png')
  592. expect(update.url).to.be.a('string')
  593. // one file upload
  594. verifyVersionIncremented(exampleProjectId, oldVersion, version, 1, done)
  595. })
  596. it('should version a replacement file', function(done) {
  597. MockDocUpdaterApi.clearProjectStructureUpdates()
  598. uploadFile(
  599. owner,
  600. exampleProjectId,
  601. rootFolderId,
  602. '2pixel.png',
  603. '1pixel.png',
  604. 'image/png',
  605. () => {
  606. const {
  607. updates,
  608. version
  609. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  610. expect(updates.length).to.equal(2)
  611. expect(updates[0].type).to.equal('rename-file')
  612. expect(updates[0].userId).to.equal(owner._id)
  613. expect(updates[0].pathname).to.equal('/1pixel.png')
  614. expect(updates[0].newPathname).to.equal('')
  615. expect(updates[1].type).to.equal('add-file')
  616. expect(updates[1].userId).to.equal(owner._id)
  617. expect(updates[1].pathname).to.equal('/1pixel.png')
  618. expect(updates[1].url).to.be.a('string')
  619. // two file uploads
  620. verifyVersionIncremented(
  621. exampleProjectId,
  622. oldVersion,
  623. version,
  624. 2,
  625. done
  626. )
  627. }
  628. )
  629. })
  630. })
  631. describe('moving entities', function() {
  632. let exampleProjectId,
  633. oldVersion,
  634. exampleDocId,
  635. exampleFileId,
  636. exampleFolderId
  637. beforeEach(function(done) {
  638. createExampleProject(owner, (err, projectId, rootFolderId) => {
  639. if (err) {
  640. return done(err)
  641. }
  642. exampleProjectId = projectId
  643. createExampleDoc(owner, projectId, (err, docId) => {
  644. if (err) {
  645. return done(err)
  646. }
  647. exampleDocId = docId
  648. uploadExampleFile(owner, projectId, rootFolderId, (err, fileId) => {
  649. if (err) {
  650. return done(err)
  651. }
  652. exampleFileId = fileId
  653. createExampleFolder(owner, projectId, (err, folderId) => {
  654. if (err) {
  655. return done(err)
  656. }
  657. exampleFolderId = folderId
  658. ProjectGetter.getProject(projectId, (error, project) => {
  659. if (error) {
  660. throw error
  661. }
  662. oldVersion = project.version
  663. MockDocUpdaterApi.clearProjectStructureUpdates()
  664. done()
  665. })
  666. })
  667. })
  668. })
  669. })
  670. })
  671. it('should version moving a doc', function(done) {
  672. moveItem(
  673. owner,
  674. exampleProjectId,
  675. 'doc',
  676. exampleDocId,
  677. exampleFolderId,
  678. () => {
  679. const {
  680. updates,
  681. version
  682. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  683. expect(updates.length).to.equal(1)
  684. const update = updates[0]
  685. expect(update.type).to.equal('rename-doc')
  686. expect(update.userId).to.equal(owner._id)
  687. expect(update.pathname).to.equal('/new.tex')
  688. expect(update.newPathname).to.equal('/foo/new.tex')
  689. // 2, because it's a delete and then add
  690. verifyVersionIncremented(
  691. exampleProjectId,
  692. oldVersion,
  693. version,
  694. 2,
  695. done
  696. )
  697. }
  698. )
  699. })
  700. it('should version moving a file', function(done) {
  701. moveItem(
  702. owner,
  703. exampleProjectId,
  704. 'file',
  705. exampleFileId,
  706. exampleFolderId,
  707. () => {
  708. const {
  709. updates,
  710. version
  711. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  712. expect(updates.length).to.equal(1)
  713. const update = updates[0]
  714. expect(update.type).to.equal('rename-file')
  715. expect(update.userId).to.equal(owner._id)
  716. expect(update.pathname).to.equal('/1pixel.png')
  717. expect(update.newPathname).to.equal('/foo/1pixel.png')
  718. // 2, because it's a delete and then add
  719. verifyVersionIncremented(
  720. exampleProjectId,
  721. oldVersion,
  722. version,
  723. 2,
  724. done
  725. )
  726. }
  727. )
  728. })
  729. it('should version moving a folder', function(done) {
  730. moveItem(
  731. owner,
  732. exampleProjectId,
  733. 'doc',
  734. exampleDocId,
  735. exampleFolderId,
  736. () => {
  737. MockDocUpdaterApi.clearProjectStructureUpdates()
  738. owner.request.post(
  739. {
  740. uri: `project/${exampleProjectId}/folder`,
  741. json: {
  742. name: 'bar'
  743. }
  744. },
  745. (error, res, body) => {
  746. if (error) {
  747. throw error
  748. }
  749. const newFolderId = body._id
  750. moveItem(
  751. owner,
  752. exampleProjectId,
  753. 'folder',
  754. exampleFolderId,
  755. newFolderId,
  756. () => {
  757. const {
  758. updates,
  759. version
  760. } = MockDocUpdaterApi.getProjectStructureUpdates(
  761. exampleProjectId
  762. )
  763. expect(updates.length).to.equal(1)
  764. let update = updates[0]
  765. expect(update.type).to.equal('rename-doc')
  766. expect(update.userId).to.equal(owner._id)
  767. expect(update.pathname).to.equal('/foo/new.tex')
  768. expect(update.newPathname).to.equal('/bar/foo/new.tex')
  769. // 5, because it's two file moves plus a folder
  770. verifyVersionIncremented(
  771. exampleProjectId,
  772. oldVersion,
  773. version,
  774. 5,
  775. done
  776. )
  777. }
  778. )
  779. }
  780. )
  781. }
  782. )
  783. })
  784. })
  785. describe('renaming entities', function() {
  786. let exampleProjectId,
  787. exampleDocId,
  788. exampleFileId,
  789. exampleFolderId,
  790. oldVersion
  791. beforeEach(function(done) {
  792. createExampleProject(owner, (err, projectId, rootFolderId) => {
  793. if (err) {
  794. return done(err)
  795. }
  796. exampleProjectId = projectId
  797. createExampleDoc(owner, projectId, (err, docId) => {
  798. if (err) {
  799. return done(err)
  800. }
  801. exampleDocId = docId
  802. uploadExampleFile(owner, projectId, rootFolderId, (err, fileId) => {
  803. if (err) {
  804. return done(err)
  805. }
  806. exampleFileId = fileId
  807. createExampleFolder(owner, projectId, (err, folderId) => {
  808. if (err) {
  809. return done(err)
  810. }
  811. exampleFolderId = folderId
  812. moveItem(owner, projectId, 'doc', docId, folderId, () => {
  813. moveItem(owner, projectId, 'file', fileId, folderId, () => {
  814. MockDocUpdaterApi.clearProjectStructureUpdates()
  815. ProjectGetter.getProject(
  816. exampleProjectId,
  817. (error, project) => {
  818. if (error) {
  819. throw error
  820. }
  821. oldVersion = project.version
  822. done()
  823. }
  824. )
  825. })
  826. })
  827. })
  828. })
  829. })
  830. })
  831. })
  832. it('should version renaming a doc', function(done) {
  833. renameItem(
  834. owner,
  835. exampleProjectId,
  836. 'Doc',
  837. exampleDocId,
  838. 'wombat.tex',
  839. () => {
  840. const {
  841. updates,
  842. version
  843. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  844. expect(updates.length).to.equal(1)
  845. const update = updates[0]
  846. expect(update.type).to.equal('rename-doc')
  847. expect(update.userId).to.equal(owner._id)
  848. expect(update.pathname).to.equal('/foo/new.tex')
  849. expect(update.newPathname).to.equal('/foo/wombat.tex')
  850. verifyVersionIncremented(
  851. exampleProjectId,
  852. oldVersion,
  853. version,
  854. 1,
  855. done
  856. )
  857. }
  858. )
  859. })
  860. it('should version renaming a file', function(done) {
  861. renameItem(
  862. owner,
  863. exampleProjectId,
  864. 'file',
  865. exampleFileId,
  866. 'potato.png',
  867. () => {
  868. const {
  869. updates,
  870. version
  871. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  872. expect(updates.length).to.equal(1)
  873. const update = updates[0]
  874. expect(update.type).to.equal('rename-file')
  875. expect(update.userId).to.equal(owner._id)
  876. expect(update.pathname).to.equal('/foo/1pixel.png')
  877. expect(update.newPathname).to.equal('/foo/potato.png')
  878. verifyVersionIncremented(
  879. exampleProjectId,
  880. oldVersion,
  881. version,
  882. 1,
  883. done
  884. )
  885. }
  886. )
  887. })
  888. it('should version renaming a folder', function(done) {
  889. renameItem(
  890. owner,
  891. exampleProjectId,
  892. 'folder',
  893. exampleFolderId,
  894. 'giraffe',
  895. () => {
  896. const {
  897. updates,
  898. version
  899. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  900. expect(updates.length).to.equal(2)
  901. expect(updates[0].type).to.equal('rename-doc')
  902. expect(updates[0].userId).to.equal(owner._id)
  903. expect(updates[0].pathname).to.equal('/foo/new.tex')
  904. expect(updates[0].newPathname).to.equal('/giraffe/new.tex')
  905. expect(updates[1].type).to.equal('rename-file')
  906. expect(updates[1].userId).to.equal(owner._id)
  907. expect(updates[1].pathname).to.equal('/foo/1pixel.png')
  908. expect(updates[1].newPathname).to.equal('/giraffe/1pixel.png')
  909. verifyVersionIncremented(
  910. exampleProjectId,
  911. oldVersion,
  912. version,
  913. 1,
  914. done
  915. )
  916. }
  917. )
  918. })
  919. })
  920. describe('deleting entities', function() {
  921. let exampleProjectId, oldVersion, exampleFolderId
  922. beforeEach(function(done) {
  923. createExampleProject(owner, (err, projectId) => {
  924. if (err) {
  925. return done(err)
  926. }
  927. exampleProjectId = projectId
  928. createExampleFolder(owner, exampleProjectId, (err, folderId) => {
  929. if (err) {
  930. return done(err)
  931. }
  932. exampleFolderId = folderId
  933. createExampleDoc(owner, projectId, (err, docId) => {
  934. if (err) {
  935. return done(err)
  936. }
  937. uploadExampleFile(owner, projectId, folderId, (err, fileId) => {
  938. if (err) {
  939. return done(err)
  940. }
  941. moveItem(owner, projectId, 'doc', docId, folderId, () => {
  942. moveItem(owner, projectId, 'file', fileId, folderId, () => {
  943. MockDocUpdaterApi.clearProjectStructureUpdates()
  944. ProjectGetter.getProject(
  945. exampleProjectId,
  946. (error, project) => {
  947. if (error) {
  948. throw error
  949. }
  950. oldVersion = project.version
  951. done()
  952. }
  953. )
  954. })
  955. })
  956. })
  957. })
  958. })
  959. })
  960. })
  961. it('should version deleting a folder', function(done) {
  962. deleteItem(owner, exampleProjectId, 'folder', exampleFolderId, () => {
  963. const {
  964. updates,
  965. version
  966. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  967. expect(updates.length).to.equal(2)
  968. expect(updates[0].type).to.equal('rename-doc')
  969. expect(updates[0].userId).to.equal(owner._id)
  970. expect(updates[0].pathname).to.equal('/foo/new.tex')
  971. expect(updates[0].newPathname).to.equal('')
  972. expect(updates[1].type).to.equal('rename-file')
  973. expect(updates[1].userId).to.equal(owner._id)
  974. expect(updates[1].pathname).to.equal('/foo/1pixel.png')
  975. expect(updates[1].newPathname).to.equal('')
  976. verifyVersionIncremented(exampleProjectId, oldVersion, version, 1, done)
  977. })
  978. })
  979. })
  980. describe('deleting docs', function() {
  981. beforeEach(function(done) {
  982. createExampleProject(owner, (err, projectId) => {
  983. if (err) {
  984. return done(err)
  985. }
  986. this.exampleProjectId = projectId
  987. createExampleFolder(owner, projectId, (err, folderId) => {
  988. if (err) {
  989. return done(err)
  990. }
  991. this.exampleFolderId = folderId
  992. createExampleDoc(owner, projectId, (err, docId) => {
  993. if (err) {
  994. return done(err)
  995. }
  996. this.exampleDocId = docId
  997. MockDocUpdaterApi.clearProjectStructureUpdates()
  998. ProjectGetter.getProject(
  999. this.exampleProjectId,
  1000. (error, project) => {
  1001. if (error) {
  1002. throw error
  1003. }
  1004. this.project0 = project
  1005. done()
  1006. }
  1007. )
  1008. })
  1009. })
  1010. })
  1011. })
  1012. describe('when rootDoc_id matches doc being deleted', function() {
  1013. beforeEach(function(done) {
  1014. Project.update(
  1015. { _id: this.exampleProjectId },
  1016. { $set: { rootDoc_id: this.exampleDocId } },
  1017. done
  1018. )
  1019. })
  1020. it('should clear rootDoc_id', function(done) {
  1021. deleteItem(
  1022. owner,
  1023. this.exampleProjectId,
  1024. 'doc',
  1025. this.exampleDocId,
  1026. () => {
  1027. ProjectGetter.getProject(
  1028. this.exampleProjectId,
  1029. (error, project) => {
  1030. if (error) {
  1031. throw error
  1032. }
  1033. expect(project.rootDoc_id).to.be.undefined
  1034. done()
  1035. }
  1036. )
  1037. }
  1038. )
  1039. })
  1040. })
  1041. describe('when rootDoc_id does not match doc being deleted', function() {
  1042. beforeEach(function(done) {
  1043. this.exampleRootDocId = new ObjectId()
  1044. Project.update(
  1045. { _id: this.exampleProjectId },
  1046. { $set: { rootDoc_id: this.exampleRootDocId } },
  1047. done
  1048. )
  1049. })
  1050. it('should not clear rootDoc_id', function(done) {
  1051. deleteItem(
  1052. owner,
  1053. this.exampleProjectId,
  1054. 'doc',
  1055. this.exampleDocId,
  1056. () => {
  1057. ProjectGetter.getProject(
  1058. this.exampleProjectId,
  1059. (error, project) => {
  1060. if (error) {
  1061. throw error
  1062. }
  1063. expect(project.rootDoc_id.toString()).to.equal(
  1064. this.exampleRootDocId.toString()
  1065. )
  1066. done()
  1067. }
  1068. )
  1069. }
  1070. )
  1071. })
  1072. })
  1073. })
  1074. describe('tpds', function() {
  1075. let projectName, exampleProjectId, oldVersion, rootFolderId
  1076. beforeEach(function(done) {
  1077. projectName = `tpds-project-${new ObjectId().toString()}`
  1078. owner.createProject(projectName, (error, projectId) => {
  1079. if (error) {
  1080. throw error
  1081. }
  1082. exampleProjectId = projectId
  1083. mkdirp(Settings.path.dumpFolder, () => {
  1084. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  1085. if (error) {
  1086. throw error
  1087. }
  1088. MockDocUpdaterApi.clearProjectStructureUpdates()
  1089. rootFolderId = project.rootFolder[0]._id.toString()
  1090. oldVersion = project.version
  1091. done()
  1092. })
  1093. })
  1094. })
  1095. })
  1096. it('should version adding a doc', function(done) {
  1097. const texFile = fs.createReadStream(
  1098. Path.resolve(Path.join(__dirname, '..', 'files', 'test.tex'))
  1099. )
  1100. const req = owner.request.post({
  1101. uri: `/user/${owner._id}/update/${projectName}/test.tex`,
  1102. auth: {
  1103. user: _.keys(Settings.httpAuthUsers)[0],
  1104. pass: _.values(Settings.httpAuthUsers)[0],
  1105. sendImmediately: true
  1106. }
  1107. })
  1108. texFile.on('error', err => {
  1109. throw err
  1110. })
  1111. req.on('error', err => {
  1112. throw err
  1113. })
  1114. req.on('response', res => {
  1115. if (res.statusCode < 200 || res.statusCode >= 300) {
  1116. throw new Error(`failed to upload file ${res.statusCode}`)
  1117. }
  1118. const {
  1119. updates,
  1120. version
  1121. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  1122. expect(updates.length).to.equal(1)
  1123. const update = updates[0]
  1124. expect(update.type).to.equal('add-doc')
  1125. expect(update.userId).to.equal(owner._id)
  1126. expect(update.pathname).to.equal('/test.tex')
  1127. expect(update.docLines).to.equal('Test')
  1128. verifyVersionIncremented(exampleProjectId, oldVersion, version, 1, done)
  1129. })
  1130. texFile.pipe(req)
  1131. })
  1132. it('should version adding a new file', function(done) {
  1133. const imageFile = fs.createReadStream(
  1134. Path.resolve(Path.join(__dirname, '..', 'files', '1pixel.png'))
  1135. )
  1136. const req = owner.request.post({
  1137. uri: `/user/${owner._id}/update/${projectName}/1pixel.png`,
  1138. auth: {
  1139. user: _.keys(Settings.httpAuthUsers)[0],
  1140. pass: _.values(Settings.httpAuthUsers)[0],
  1141. sendImmediately: true
  1142. }
  1143. })
  1144. imageFile.on('error', err => {
  1145. throw err
  1146. })
  1147. req.on('error', err => {
  1148. throw err
  1149. })
  1150. req.on('response', res => {
  1151. if (res.statusCode < 200 || res.statusCode >= 300) {
  1152. throw new Error(`failed to upload file ${res.statusCode}`)
  1153. }
  1154. const {
  1155. updates,
  1156. version
  1157. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  1158. expect(updates.length).to.equal(1)
  1159. const update = updates[0]
  1160. expect(update.type).to.equal('add-file')
  1161. expect(update.userId).to.equal(owner._id)
  1162. expect(update.pathname).to.equal('/1pixel.png')
  1163. expect(update.url).to.be.a('string')
  1164. verifyVersionIncremented(exampleProjectId, oldVersion, version, 1, done)
  1165. })
  1166. imageFile.pipe(req)
  1167. })
  1168. describe('when there are files in the project', function() {
  1169. beforeEach(function(done) {
  1170. uploadExampleFile(owner, exampleProjectId, rootFolderId, () => {
  1171. createExampleDoc(owner, exampleProjectId, () => {
  1172. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  1173. if (error) {
  1174. throw error
  1175. }
  1176. MockDocUpdaterApi.clearProjectStructureUpdates()
  1177. oldVersion = project.version
  1178. done()
  1179. })
  1180. })
  1181. })
  1182. })
  1183. it('should version replacing a file', function(done) {
  1184. const imageFile = fs.createReadStream(
  1185. Path.resolve(Path.join(__dirname, '..', 'files', '2pixel.png'))
  1186. )
  1187. const req = owner.request.post({
  1188. uri: `/user/${owner._id}/update/${projectName}/1pixel.png`,
  1189. auth: {
  1190. user: _.keys(Settings.httpAuthUsers)[0],
  1191. pass: _.values(Settings.httpAuthUsers)[0],
  1192. sendImmediately: true
  1193. }
  1194. })
  1195. imageFile.on('error', err => {
  1196. throw err
  1197. })
  1198. req.on('error', err => {
  1199. throw err
  1200. })
  1201. req.on('response', res => {
  1202. if (res.statusCode < 200 || res.statusCode >= 300) {
  1203. throw new Error(`failed to upload file ${res.statusCode}`)
  1204. }
  1205. const {
  1206. updates,
  1207. version
  1208. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  1209. expect(updates.length).to.equal(2)
  1210. expect(updates[0].type).to.equal('rename-file')
  1211. expect(updates[0].userId).to.equal(owner._id)
  1212. expect(updates[0].pathname).to.equal('/1pixel.png')
  1213. expect(updates[0].newPathname).to.equal('')
  1214. expect(updates[1].type).to.equal('add-file')
  1215. expect(updates[1].userId).to.equal(owner._id)
  1216. expect(updates[1].pathname).to.equal('/1pixel.png')
  1217. expect(updates[1].url).to.be.a('string')
  1218. verifyVersionIncremented(
  1219. exampleProjectId,
  1220. oldVersion,
  1221. version,
  1222. 1,
  1223. done
  1224. )
  1225. })
  1226. imageFile.pipe(req)
  1227. })
  1228. it('should version deleting a doc', function(done) {
  1229. owner.request.delete(
  1230. {
  1231. uri: `/user/${owner._id}/update/${projectName}/new.tex`,
  1232. auth: {
  1233. user: _.keys(Settings.httpAuthUsers)[0],
  1234. pass: _.values(Settings.httpAuthUsers)[0],
  1235. sendImmediately: true
  1236. }
  1237. },
  1238. (error, res) => {
  1239. if (error) {
  1240. throw error
  1241. }
  1242. if (res.statusCode < 200 || res.statusCode >= 300) {
  1243. throw new Error(`failed to delete doc ${res.statusCode}`)
  1244. }
  1245. const {
  1246. updates,
  1247. version
  1248. } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId)
  1249. expect(updates.length).to.equal(1)
  1250. const update = updates[0]
  1251. expect(update.type).to.equal('rename-doc')
  1252. expect(update.userId).to.equal(owner._id)
  1253. expect(update.pathname).to.equal('/new.tex')
  1254. expect(update.newPathname).to.equal('')
  1255. verifyVersionIncremented(
  1256. exampleProjectId,
  1257. oldVersion,
  1258. version,
  1259. 1,
  1260. done
  1261. )
  1262. }
  1263. )
  1264. })
  1265. })
  1266. })
  1267. describe('uploading a document', function() {
  1268. let exampleProjectId, rootFolderId
  1269. beforeEach(function(done) {
  1270. createExampleProject(owner, (err, projectId, folderId) => {
  1271. if (err) {
  1272. return done(err)
  1273. }
  1274. exampleProjectId = projectId
  1275. rootFolderId = folderId
  1276. MockDocUpdaterApi.clearProjectStructureUpdates()
  1277. done()
  1278. })
  1279. })
  1280. describe('with an unusual character set', function() {
  1281. it('should correctly handle utf16-le data', function(done) {
  1282. uploadFile(
  1283. owner,
  1284. exampleProjectId,
  1285. rootFolderId,
  1286. 'charsets/test-greek-utf16-le-bom.tex',
  1287. 'test-greek-utf16-le-bom.tex',
  1288. 'text/x-tex',
  1289. () => {
  1290. const { updates } = MockDocUpdaterApi.getProjectStructureUpdates(
  1291. exampleProjectId
  1292. )
  1293. expect(updates.length).to.equal(1)
  1294. const update = updates[0]
  1295. expect(update.type).to.equal('add-doc')
  1296. expect(update.pathname).to.equal('/test-greek-utf16-le-bom.tex')
  1297. expect(update.docLines).to.contain(
  1298. 'Η γρήγορη καστανή αλεπού πήδηξε χαλαρά πάνω από το σκυλί.'
  1299. )
  1300. done()
  1301. }
  1302. )
  1303. })
  1304. it('should correctly handle windows1252/iso-8859-1/latin1 data', function(done) {
  1305. uploadFile(
  1306. owner,
  1307. exampleProjectId,
  1308. rootFolderId,
  1309. 'charsets/test-german-windows-1252.tex',
  1310. 'test-german-windows-1252.tex',
  1311. 'text/x-tex',
  1312. () => {
  1313. const { updates } = MockDocUpdaterApi.getProjectStructureUpdates(
  1314. exampleProjectId
  1315. )
  1316. expect(updates.length).to.equal(1)
  1317. const update = updates[0]
  1318. expect(update.type).to.equal('add-doc')
  1319. expect(update.pathname).to.equal('/test-german-windows-1252.tex')
  1320. expect(update.docLines).to.contain(
  1321. 'Der schnelle braune Fuchs sprang träge über den Hund.'
  1322. )
  1323. done()
  1324. }
  1325. )
  1326. })
  1327. })
  1328. })
  1329. })