LinkedFilesTests.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. max-len,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const async = require('async')
  16. const { expect } = require('chai')
  17. const _ = require('underscore')
  18. const mkdirp = require('mkdirp')
  19. const Settings = require('settings-sharelatex')
  20. const MockFileStoreApi = require('./helpers/MockFileStoreApi')
  21. const request = require('./helpers/request')
  22. const User = require('./helpers/User')
  23. const MockClsiApi = require('./helpers/MockClsiApi')
  24. const express = require('express')
  25. const LinkedUrlProxy = express()
  26. LinkedUrlProxy.get('/', (req, res, next) => {
  27. if (req.query.url === 'http://example.com/foo') {
  28. return res.send('foo foo foo')
  29. } else if (req.query.url === 'http://example.com/bar') {
  30. return res.send('bar bar bar')
  31. } else {
  32. return res.sendStatus(404)
  33. }
  34. })
  35. describe('LinkedFiles', function() {
  36. before(function(done) {
  37. return LinkedUrlProxy.listen(6543, error => {
  38. if (error != null) {
  39. return done(error)
  40. }
  41. this.owner = new User()
  42. return this.owner.login(() => mkdirp(Settings.path.dumpFolder, done))
  43. })
  44. })
  45. describe('creating a project linked file', function() {
  46. before(function(done) {
  47. this.source_doc_name = 'test.txt'
  48. return async.series(
  49. [
  50. cb => {
  51. return this.owner.createProject(
  52. 'plf-test-one',
  53. { template: 'blank' },
  54. (error, project_id) => {
  55. this.project_one_id = project_id
  56. return cb(error)
  57. }
  58. )
  59. },
  60. cb => {
  61. return this.owner.getProject(
  62. this.project_one_id,
  63. (error, project) => {
  64. this.project_one = project
  65. this.project_one_root_folder_id = project.rootFolder[0]._id.toString()
  66. return cb(error)
  67. }
  68. )
  69. },
  70. cb => {
  71. return this.owner.createProject(
  72. 'plf-test-two',
  73. { template: 'blank' },
  74. (error, project_id) => {
  75. this.project_two_id = project_id
  76. return cb(error)
  77. }
  78. )
  79. },
  80. cb => {
  81. return this.owner.getProject(
  82. this.project_two_id,
  83. (error, project) => {
  84. this.project_two = project
  85. this.project_two_root_folder_id = project.rootFolder[0]._id.toString()
  86. return cb(error)
  87. }
  88. )
  89. },
  90. cb => {
  91. return this.owner.createDocInProject(
  92. this.project_two_id,
  93. this.project_two_root_folder_id,
  94. this.source_doc_name,
  95. (error, doc_id) => {
  96. this.source_doc_id = doc_id
  97. return cb(error)
  98. }
  99. )
  100. },
  101. cb => {
  102. return this.owner.createDocInProject(
  103. this.project_two_id,
  104. this.project_two_root_folder_id,
  105. 'some-harmless-doc.txt',
  106. (error, doc_id) => {
  107. return cb(error)
  108. }
  109. )
  110. }
  111. ],
  112. done
  113. )
  114. })
  115. it('should produce a list of the users projects', function(done) {
  116. return this.owner.request.get(
  117. {
  118. url: '/user/projects',
  119. json: true
  120. },
  121. (err, response, body) => {
  122. expect(err).to.not.exist
  123. expect(body).to.deep.equal({
  124. projects: [
  125. {
  126. _id: this.project_one_id,
  127. name: 'plf-test-one',
  128. accessLevel: 'owner'
  129. },
  130. {
  131. _id: this.project_two_id,
  132. name: 'plf-test-two',
  133. accessLevel: 'owner'
  134. }
  135. ]
  136. })
  137. return done()
  138. }
  139. )
  140. })
  141. it('should produce a list of entities in the project', function(done) {
  142. return this.owner.request.get(
  143. {
  144. url: `/project/${this.project_two_id}/entities`,
  145. json: true
  146. },
  147. (err, response, body) => {
  148. expect(err).to.not.exist
  149. expect(body).to.deep.equal({
  150. project_id: this.project_two_id,
  151. entities: [
  152. { path: '/main.tex', type: 'doc' },
  153. { path: '/some-harmless-doc.txt', type: 'doc' },
  154. { path: '/test.txt', type: 'doc' }
  155. ]
  156. })
  157. return done()
  158. }
  159. )
  160. })
  161. it('should import a file from the source project', function(done) {
  162. return this.owner.request.post(
  163. {
  164. url: `/project/${this.project_one_id}/linked_file`,
  165. json: {
  166. name: 'test-link.txt',
  167. parent_folder_id: this.project_one_root_folder_id,
  168. provider: 'project_file',
  169. data: {
  170. source_project_id: this.project_two_id,
  171. source_entity_path: `/${this.source_doc_name}`
  172. }
  173. }
  174. },
  175. (error, response, body) => {
  176. expect(response.statusCode).to.equal(200)
  177. const { new_file_id } = body
  178. this.existing_file_id = new_file_id
  179. expect(new_file_id).to.exist
  180. return this.owner.getProject(
  181. this.project_one_id,
  182. (error, project) => {
  183. if (error != null) {
  184. return done(error)
  185. }
  186. const firstFile = project.rootFolder[0].fileRefs[0]
  187. expect(firstFile._id.toString()).to.equal(new_file_id.toString())
  188. expect(firstFile.linkedFileData).to.deep.equal({
  189. provider: 'project_file',
  190. source_project_id: this.project_two_id,
  191. source_entity_path: `/${this.source_doc_name}`
  192. })
  193. expect(firstFile.name).to.equal('test-link.txt')
  194. return done()
  195. }
  196. )
  197. }
  198. )
  199. })
  200. it('should refresh the file', function(done) {
  201. return this.owner.request.post(
  202. {
  203. url: `/project/${this.project_one_id}/linked_file/${
  204. this.existing_file_id
  205. }/refresh`,
  206. json: true
  207. },
  208. (error, response, body) => {
  209. expect(response.statusCode).to.equal(200)
  210. const { new_file_id } = body
  211. expect(new_file_id).to.exist
  212. expect(new_file_id).to.not.equal(this.existing_file_id)
  213. this.refreshed_file_id = new_file_id
  214. return this.owner.getProject(
  215. this.project_one_id,
  216. (error, project) => {
  217. if (error != null) {
  218. return done(error)
  219. }
  220. const firstFile = project.rootFolder[0].fileRefs[0]
  221. expect(firstFile._id.toString()).to.equal(new_file_id.toString())
  222. expect(firstFile.name).to.equal('test-link.txt')
  223. return done()
  224. }
  225. )
  226. }
  227. )
  228. })
  229. return it('should not allow to create a linked-file with v1 id', function(done) {
  230. return this.owner.request.post(
  231. {
  232. url: `/project/${this.project_one_id}/linked_file`,
  233. json: {
  234. name: 'test-link-should-not-work.txt',
  235. parent_folder_id: this.project_one_root_folder_id,
  236. provider: 'project_file',
  237. data: {
  238. v1_source_doc_id: 1234,
  239. source_entity_path: `/${this.source_doc_name}`
  240. }
  241. }
  242. },
  243. (error, response, body) => {
  244. expect(response.statusCode).to.equal(403)
  245. expect(body).to.equal('You do not have access to this project')
  246. return done()
  247. }
  248. )
  249. })
  250. })
  251. describe('with a linked project_file from a v1 project that has not been imported', function() {
  252. before(function(done) {
  253. return async.series(
  254. [
  255. cb => {
  256. return this.owner.createProject(
  257. 'plf-v1-test-one',
  258. { template: 'blank' },
  259. (error, project_id) => {
  260. this.project_one_id = project_id
  261. return cb(error)
  262. }
  263. )
  264. },
  265. cb => {
  266. return this.owner.getProject(
  267. this.project_one_id,
  268. (error, project) => {
  269. this.project_one = project
  270. this.project_one_root_folder_id = project.rootFolder[0]._id.toString()
  271. this.project_one.rootFolder[0].fileRefs.push({
  272. linkedFileData: {
  273. provider: 'project_file',
  274. v1_source_doc_id: 9999999, // We won't find this id in the database
  275. source_entity_path: 'example.jpeg'
  276. },
  277. _id: 'abcd',
  278. rev: 0,
  279. created: new Date(),
  280. name: 'example.jpeg'
  281. })
  282. return this.owner.saveProject(this.project_one, cb)
  283. }
  284. )
  285. }
  286. ],
  287. done
  288. )
  289. })
  290. return it('should refuse to refresh', function(done) {
  291. return this.owner.request.post(
  292. {
  293. url: `/project/${this.project_one_id}/linked_file/abcd/refresh`,
  294. json: true
  295. },
  296. (error, response, body) => {
  297. expect(response.statusCode).to.equal(409)
  298. expect(body).to.equal(
  299. 'Sorry, the source project is not yet imported to Overleaf v2. Please import it to Overleaf v2 to refresh this file'
  300. )
  301. return done()
  302. }
  303. )
  304. })
  305. })
  306. describe('creating a URL based linked file', function() {
  307. before(function(done) {
  308. return this.owner.createProject(
  309. 'url-linked-files-project',
  310. { template: 'blank' },
  311. (error, project_id) => {
  312. if (error != null) {
  313. throw error
  314. }
  315. this.project_id = project_id
  316. return this.owner.getProject(project_id, (error, project) => {
  317. if (error != null) {
  318. throw error
  319. }
  320. this.project = project
  321. this.root_folder_id = project.rootFolder[0]._id.toString()
  322. return done()
  323. })
  324. }
  325. )
  326. })
  327. it('should download the URL and create a file with the contents and linkedFileData', function(done) {
  328. return this.owner.request.post(
  329. {
  330. url: `/project/${this.project_id}/linked_file`,
  331. json: {
  332. provider: 'url',
  333. data: {
  334. url: 'http://example.com/foo'
  335. },
  336. parent_folder_id: this.root_folder_id,
  337. name: 'url-test-file-1'
  338. }
  339. },
  340. (error, response, body) => {
  341. if (error != null) {
  342. throw error
  343. }
  344. expect(response.statusCode).to.equal(200)
  345. return this.owner.getProject(this.project_id, (error, project) => {
  346. if (error != null) {
  347. throw error
  348. }
  349. const file = project.rootFolder[0].fileRefs[0]
  350. expect(file.linkedFileData).to.deep.equal({
  351. provider: 'url',
  352. url: 'http://example.com/foo'
  353. })
  354. return this.owner.request.get(
  355. `/project/${this.project_id}/file/${file._id}`,
  356. function(error, response, body) {
  357. if (error != null) {
  358. throw error
  359. }
  360. expect(response.statusCode).to.equal(200)
  361. expect(body).to.equal('foo foo foo')
  362. return done()
  363. }
  364. )
  365. })
  366. }
  367. )
  368. })
  369. it('should replace and update a URL based linked file', function(done) {
  370. return this.owner.request.post(
  371. {
  372. url: `/project/${this.project_id}/linked_file`,
  373. json: {
  374. provider: 'url',
  375. data: {
  376. url: 'http://example.com/foo'
  377. },
  378. parent_folder_id: this.root_folder_id,
  379. name: 'url-test-file-2'
  380. }
  381. },
  382. (error, response, body) => {
  383. if (error != null) {
  384. throw error
  385. }
  386. expect(response.statusCode).to.equal(200)
  387. return this.owner.request.post(
  388. {
  389. url: `/project/${this.project_id}/linked_file`,
  390. json: {
  391. provider: 'url',
  392. data: {
  393. url: 'http://example.com/bar'
  394. },
  395. parent_folder_id: this.root_folder_id,
  396. name: 'url-test-file-2'
  397. }
  398. },
  399. (error, response, body) => {
  400. if (error != null) {
  401. throw error
  402. }
  403. expect(response.statusCode).to.equal(200)
  404. return this.owner.getProject(
  405. this.project_id,
  406. (error, project) => {
  407. if (error != null) {
  408. throw error
  409. }
  410. const file = project.rootFolder[0].fileRefs[1]
  411. expect(file.linkedFileData).to.deep.equal({
  412. provider: 'url',
  413. url: 'http://example.com/bar'
  414. })
  415. return this.owner.request.get(
  416. `/project/${this.project_id}/file/${file._id}`,
  417. function(error, response, body) {
  418. if (error != null) {
  419. throw error
  420. }
  421. expect(response.statusCode).to.equal(200)
  422. expect(body).to.equal('bar bar bar')
  423. return done()
  424. }
  425. )
  426. }
  427. )
  428. }
  429. )
  430. }
  431. )
  432. })
  433. it('should return an error if the URL does not succeed', function(done) {
  434. return this.owner.request.post(
  435. {
  436. url: `/project/${this.project_id}/linked_file`,
  437. json: {
  438. provider: 'url',
  439. data: {
  440. url: 'http://example.com/does-not-exist'
  441. },
  442. parent_folder_id: this.root_folder_id,
  443. name: 'url-test-file-3'
  444. }
  445. },
  446. (error, response, body) => {
  447. if (error != null) {
  448. throw error
  449. }
  450. expect(response.statusCode).to.equal(422) // unprocessable
  451. expect(body).to.equal(
  452. 'Your URL could not be reached (404 status code). Please check it and try again.'
  453. )
  454. return done()
  455. }
  456. )
  457. })
  458. it('should return an error if the URL is invalid', function(done) {
  459. return this.owner.request.post(
  460. {
  461. url: `/project/${this.project_id}/linked_file`,
  462. json: {
  463. provider: 'url',
  464. data: {
  465. url: '!^$%'
  466. },
  467. parent_folder_id: this.root_folder_id,
  468. name: 'url-test-file-4'
  469. }
  470. },
  471. (error, response, body) => {
  472. if (error != null) {
  473. throw error
  474. }
  475. expect(response.statusCode).to.equal(422) // unprocessable
  476. expect(body).to.equal(
  477. 'Your URL is not valid. Please check it and try again.'
  478. )
  479. return done()
  480. }
  481. )
  482. })
  483. it('should return an error if the URL uses a non-http protocol', function(done) {
  484. return this.owner.request.post(
  485. {
  486. url: `/project/${this.project_id}/linked_file`,
  487. json: {
  488. provider: 'url',
  489. data: {
  490. url: 'ftp://localhost'
  491. },
  492. parent_folder_id: this.root_folder_id,
  493. name: 'url-test-file-5'
  494. }
  495. },
  496. (error, response, body) => {
  497. if (error != null) {
  498. throw error
  499. }
  500. expect(response.statusCode).to.equal(422) // unprocessable
  501. expect(body).to.equal(
  502. 'Your URL is not valid. Please check it and try again.'
  503. )
  504. return done()
  505. }
  506. )
  507. })
  508. return it('should accept a URL withuot a leading http://, and add it', function(done) {
  509. return this.owner.request.post(
  510. {
  511. url: `/project/${this.project_id}/linked_file`,
  512. json: {
  513. provider: 'url',
  514. data: {
  515. url: 'example.com/foo'
  516. },
  517. parent_folder_id: this.root_folder_id,
  518. name: 'url-test-file-6'
  519. }
  520. },
  521. (error, response, body) => {
  522. if (error != null) {
  523. throw error
  524. }
  525. expect(response.statusCode).to.equal(200)
  526. return this.owner.getProject(this.project_id, (error, project) => {
  527. if (error != null) {
  528. throw error
  529. }
  530. const file = _.find(
  531. project.rootFolder[0].fileRefs,
  532. file => file.name === 'url-test-file-6'
  533. )
  534. expect(file.linkedFileData).to.deep.equal({
  535. provider: 'url',
  536. url: 'http://example.com/foo'
  537. })
  538. return this.owner.request.get(
  539. `/project/${this.project_id}/file/${file._id}`,
  540. function(error, response, body) {
  541. if (error != null) {
  542. throw error
  543. }
  544. expect(response.statusCode).to.equal(200)
  545. expect(body).to.equal('foo foo foo')
  546. return done()
  547. }
  548. )
  549. })
  550. }
  551. )
  552. })
  553. })
  554. // TODO: Add test for asking for host that return ENOTFOUND
  555. // (This will probably end up handled by the proxy)
  556. describe('creating a linked output file', function() {
  557. before(function(done) {
  558. return async.series(
  559. [
  560. cb => {
  561. return this.owner.createProject(
  562. 'output-test-one',
  563. { template: 'blank' },
  564. (error, project_id) => {
  565. this.project_one_id = project_id
  566. return cb(error)
  567. }
  568. )
  569. },
  570. cb => {
  571. return this.owner.getProject(
  572. this.project_one_id,
  573. (error, project) => {
  574. this.project_one = project
  575. this.project_one_root_folder_id = project.rootFolder[0]._id.toString()
  576. return cb(error)
  577. }
  578. )
  579. },
  580. cb => {
  581. return this.owner.createProject(
  582. 'output-test-two',
  583. { template: 'blank' },
  584. (error, project_id) => {
  585. this.project_two_id = project_id
  586. return cb(error)
  587. }
  588. )
  589. },
  590. cb => {
  591. return this.owner.getProject(
  592. this.project_two_id,
  593. (error, project) => {
  594. this.project_two = project
  595. this.project_two_root_folder_id = project.rootFolder[0]._id.toString()
  596. return cb(error)
  597. }
  598. )
  599. }
  600. ],
  601. done
  602. )
  603. })
  604. it('should import the project.pdf file from the source project', function(done) {
  605. return this.owner.request.post(
  606. {
  607. url: `/project/${this.project_one_id}/linked_file`,
  608. json: {
  609. name: 'test.pdf',
  610. parent_folder_id: this.project_one_root_folder_id,
  611. provider: 'project_output_file',
  612. data: {
  613. source_project_id: this.project_two_id,
  614. source_output_file_path: 'project.pdf',
  615. build_id: '1234-abcd'
  616. }
  617. }
  618. },
  619. (error, response, body) => {
  620. const { new_file_id } = body
  621. this.existing_file_id = new_file_id
  622. expect(new_file_id).to.exist
  623. return this.owner.getProject(
  624. this.project_one_id,
  625. (error, project) => {
  626. if (error != null) {
  627. return done(error)
  628. }
  629. const firstFile = project.rootFolder[0].fileRefs[0]
  630. expect(firstFile._id.toString()).to.equal(new_file_id.toString())
  631. expect(firstFile.linkedFileData).to.deep.equal({
  632. provider: 'project_output_file',
  633. source_project_id: this.project_two_id,
  634. source_output_file_path: 'project.pdf',
  635. build_id: '1234-abcd'
  636. })
  637. expect(firstFile.name).to.equal('test.pdf')
  638. return done()
  639. }
  640. )
  641. }
  642. )
  643. })
  644. return it('should refresh the file', function(done) {
  645. return this.owner.request.post(
  646. {
  647. url: `/project/${this.project_one_id}/linked_file/${
  648. this.existing_file_id
  649. }/refresh`,
  650. json: true
  651. },
  652. (error, response, body) => {
  653. const { new_file_id } = body
  654. expect(new_file_id).to.exist
  655. expect(new_file_id).to.not.equal(this.existing_file_id)
  656. this.refreshed_file_id = new_file_id
  657. return this.owner.getProject(
  658. this.project_one_id,
  659. (error, project) => {
  660. if (error != null) {
  661. return done(error)
  662. }
  663. const firstFile = project.rootFolder[0].fileRefs[0]
  664. expect(firstFile._id.toString()).to.equal(new_file_id.toString())
  665. expect(firstFile.name).to.equal('test.pdf')
  666. return done()
  667. }
  668. )
  669. }
  670. )
  671. })
  672. })
  673. return describe('with a linked project_output_file from a v1 project that has not been imported', function() {
  674. before(function(done) {
  675. return async.series(
  676. [
  677. cb => {
  678. return this.owner.createProject(
  679. 'output-v1-test-one',
  680. { template: 'blank' },
  681. (error, project_id) => {
  682. this.project_one_id = project_id
  683. return cb(error)
  684. }
  685. )
  686. },
  687. cb => {
  688. return this.owner.getProject(
  689. this.project_one_id,
  690. (error, project) => {
  691. this.project_one = project
  692. this.project_one_root_folder_id = project.rootFolder[0]._id.toString()
  693. this.project_one.rootFolder[0].fileRefs.push({
  694. linkedFileData: {
  695. provider: 'project_output_file',
  696. v1_source_doc_id: 9999999, // We won't find this id in the database
  697. source_output_file_path: 'project.pdf'
  698. },
  699. _id: 'abcdef',
  700. rev: 0,
  701. created: new Date(),
  702. name: 'whatever.pdf'
  703. })
  704. return this.owner.saveProject(this.project_one, cb)
  705. }
  706. )
  707. }
  708. ],
  709. done
  710. )
  711. })
  712. return it('should refuse to refresh', function(done) {
  713. return this.owner.request.post(
  714. {
  715. url: `/project/${this.project_one_id}/linked_file/abcdef/refresh`,
  716. json: true
  717. },
  718. (error, response, body) => {
  719. expect(response.statusCode).to.equal(409)
  720. expect(body).to.equal(
  721. 'Sorry, the source project is not yet imported to Overleaf v2. Please import it to Overleaf v2 to refresh this file'
  722. )
  723. return done()
  724. }
  725. )
  726. })
  727. })
  728. })