ProjectStructureTests.js 40 KB

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