LinkedFilesTests.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. const { expect } = require('chai')
  2. const _ = require('underscore')
  3. const mkdirp = require('mkdirp')
  4. const { promisify } = require('util')
  5. const Settings = require('settings-sharelatex')
  6. const User = require('./helpers/User').promises
  7. require('./helpers/MockFileStoreApi')
  8. require('./helpers/MockClsiApi')
  9. const express = require('express')
  10. const LinkedUrlProxy = express()
  11. LinkedUrlProxy.get('/', (req, res, next) => {
  12. if (req.query.url === 'http://example.com/foo') {
  13. return res.send('foo foo foo')
  14. } else if (req.query.url === 'http://example.com/bar') {
  15. return res.send('bar bar bar')
  16. } else {
  17. return res.sendStatus(404)
  18. }
  19. })
  20. describe('LinkedFiles', function() {
  21. let projectOne, projectOneId, projectOneRootFolderId
  22. let projectTwo, projectTwoId, projectTwoRootFolderId
  23. let sourceDocName = 'test.txt'
  24. let owner
  25. before(function(done) {
  26. LinkedUrlProxy.listen(6543, done)
  27. })
  28. beforeEach(async function() {
  29. owner = new User()
  30. await owner.login()
  31. await promisify(mkdirp)(Settings.path.dumpFolder)
  32. })
  33. describe('creating a project linked file', function() {
  34. beforeEach(async function() {
  35. projectOneId = await owner.createProject('plf-test-one', {
  36. template: 'blank'
  37. })
  38. projectOne = await owner.getProject(projectOneId)
  39. projectOneRootFolderId = projectOne.rootFolder[0]._id.toString()
  40. projectTwoId = await owner.createProject('plf-test-two', {
  41. template: 'blank'
  42. })
  43. projectTwo = await owner.getProject(projectTwoId)
  44. projectTwoRootFolderId = projectTwo.rootFolder[0]._id.toString()
  45. await owner.createDocInProject(
  46. projectTwoId,
  47. projectTwoRootFolderId,
  48. sourceDocName
  49. )
  50. await owner.createDocInProject(
  51. projectTwoId,
  52. projectTwoRootFolderId,
  53. 'some-harmless-doc.txt'
  54. )
  55. })
  56. it('should produce a list of the users projects and their entities', async function() {
  57. let { body } = await owner.doRequest('get', {
  58. url: '/user/projects',
  59. json: true
  60. })
  61. expect(body).to.deep.equal({
  62. projects: [
  63. {
  64. _id: projectOneId,
  65. name: 'plf-test-one',
  66. accessLevel: 'owner'
  67. },
  68. {
  69. _id: projectTwoId,
  70. name: 'plf-test-two',
  71. accessLevel: 'owner'
  72. }
  73. ]
  74. })
  75. ;({ body } = await owner.doRequest('get', {
  76. url: `/project/${projectTwoId}/entities`,
  77. json: true
  78. }))
  79. expect(body).to.deep.equal({
  80. project_id: projectTwoId,
  81. entities: [
  82. { path: '/main.tex', type: 'doc' },
  83. { path: '/some-harmless-doc.txt', type: 'doc' },
  84. { path: '/test.txt', type: 'doc' }
  85. ]
  86. })
  87. })
  88. it('should import a file and refresh it if there is no v1 id', async function() {
  89. // import the file from the source project
  90. let { response, body } = await owner.doRequest('post', {
  91. url: `/project/${projectOneId}/linked_file`,
  92. json: {
  93. name: 'test-link.txt',
  94. parent_folder_id: projectOneRootFolderId,
  95. provider: 'project_file',
  96. data: {
  97. source_project_id: projectTwoId,
  98. source_entity_path: `/${sourceDocName}`
  99. }
  100. }
  101. })
  102. expect(response.statusCode).to.equal(200)
  103. const existingFileId = body.new_file_id
  104. expect(existingFileId).to.exist
  105. let updatedProjectOne = await owner.getProject(projectOneId)
  106. let firstFile = updatedProjectOne.rootFolder[0].fileRefs[0]
  107. expect(firstFile._id.toString()).to.equal(existingFileId.toString())
  108. expect(firstFile.linkedFileData).to.deep.equal({
  109. provider: 'project_file',
  110. source_project_id: projectTwoId,
  111. source_entity_path: `/${sourceDocName}`
  112. })
  113. expect(firstFile.name).to.equal('test-link.txt')
  114. // refresh the file
  115. ;({ response, body } = await owner.doRequest('post', {
  116. url: `/project/${projectOneId}/linked_file/${existingFileId}/refresh`,
  117. json: true
  118. }))
  119. expect(response.statusCode).to.equal(200)
  120. const newFileId = body.new_file_id
  121. expect(newFileId).to.exist
  122. expect(newFileId).to.not.equal(existingFileId)
  123. updatedProjectOne = await owner.getProject(projectOneId)
  124. firstFile = updatedProjectOne.rootFolder[0].fileRefs[0]
  125. expect(firstFile._id.toString()).to.equal(newFileId.toString())
  126. expect(firstFile.name).to.equal('test-link.txt')
  127. // should not work if there is a v1 id
  128. ;({ response, body } = await owner.doRequest('post', {
  129. url: `/project/${projectOneId}/linked_file`,
  130. json: {
  131. name: 'test-link-should-not-work.txt',
  132. parent_folder_id: projectOneRootFolderId,
  133. provider: 'project_file',
  134. data: {
  135. v1_source_doc_id: 1234,
  136. source_entity_path: `/${sourceDocName}`
  137. }
  138. }
  139. }))
  140. expect(response.statusCode).to.equal(403)
  141. expect(body).to.equal('You do not have access to this project')
  142. })
  143. })
  144. describe('with a linked project_file from a v1 project that has not been imported', function() {
  145. beforeEach(async function() {
  146. projectOneId = await owner.createProject('plf-v1-test-one', {
  147. template: 'blank'
  148. })
  149. projectOne = await owner.getProject(projectOneId)
  150. projectOneRootFolderId = projectOne.rootFolder[0]._id.toString()
  151. projectOne.rootFolder[0].fileRefs.push({
  152. linkedFileData: {
  153. provider: 'project_file',
  154. v1_source_doc_id: 9999999, // We won't find this id in the database
  155. source_entity_path: 'example.jpeg'
  156. },
  157. _id: 'abcd',
  158. rev: 0,
  159. created: new Date(),
  160. name: 'example.jpeg'
  161. })
  162. await owner.saveProject(projectOne)
  163. })
  164. it('should refuse to refresh', async function() {
  165. const { response, body } = await owner.doRequest('post', {
  166. url: `/project/${projectOneId}/linked_file/abcd/refresh`,
  167. json: true
  168. })
  169. expect(response.statusCode).to.equal(409)
  170. expect(body).to.equal(
  171. 'Sorry, the source project is not yet imported to Overleaf v2. Please import it to Overleaf v2 to refresh this file'
  172. )
  173. })
  174. })
  175. describe('creating a URL based linked file', function() {
  176. beforeEach(async function() {
  177. projectOneId = await owner.createProject('url-linked-files-project', {
  178. template: 'blank'
  179. })
  180. projectOne = await owner.getProject(projectOneId)
  181. projectOneRootFolderId = projectOne.rootFolder[0]._id.toString()
  182. })
  183. it('should download, create and replace a file', async function() {
  184. // downloading the initial file
  185. let { response, body } = await owner.doRequest('post', {
  186. url: `/project/${projectOneId}/linked_file`,
  187. json: {
  188. provider: 'url',
  189. data: {
  190. url: 'http://example.com/foo'
  191. },
  192. parent_folder_id: projectOneRootFolderId,
  193. name: 'url-test-file-1'
  194. }
  195. })
  196. expect(response.statusCode).to.equal(200)
  197. let updatedProject = await owner.getProject(projectOneId)
  198. let file = updatedProject.rootFolder[0].fileRefs[0]
  199. expect(file.linkedFileData).to.deep.equal({
  200. provider: 'url',
  201. url: 'http://example.com/foo'
  202. })
  203. ;({ response, body } = await owner.doRequest(
  204. 'get',
  205. `/project/${projectOneId}/file/${file._id}`
  206. ))
  207. expect(response.statusCode).to.equal(200)
  208. expect(body).to.equal('foo foo foo')
  209. // replacing the file
  210. ;({ response, body } = await owner.doRequest('post', {
  211. url: `/project/${projectOneId}/linked_file`,
  212. json: {
  213. provider: 'url',
  214. data: {
  215. url: 'http://example.com/foo'
  216. },
  217. parent_folder_id: projectOneRootFolderId,
  218. name: 'url-test-file-2'
  219. }
  220. }))
  221. expect(response.statusCode).to.equal(200)
  222. ;({ response, body } = await owner.doRequest('post', {
  223. url: `/project/${projectOneId}/linked_file`,
  224. json: {
  225. provider: 'url',
  226. data: {
  227. url: 'http://example.com/bar'
  228. },
  229. parent_folder_id: projectOneRootFolderId,
  230. name: 'url-test-file-2'
  231. }
  232. }))
  233. expect(response.statusCode).to.equal(200)
  234. updatedProject = await owner.getProject(projectOneId)
  235. file = updatedProject.rootFolder[0].fileRefs[1]
  236. expect(file.linkedFileData).to.deep.equal({
  237. provider: 'url',
  238. url: 'http://example.com/bar'
  239. })
  240. ;({ response, body } = await owner.doRequest(
  241. 'get',
  242. `/project/${projectOneId}/file/${file._id}`
  243. ))
  244. expect(response.statusCode).to.equal(200)
  245. expect(body).to.equal('bar bar bar')
  246. })
  247. it("should return an error if the file can't be downloaded", async function() {
  248. // download does not succeed
  249. let { response, body } = await owner.doRequest('post', {
  250. url: `/project/${projectOneId}/linked_file`,
  251. json: {
  252. provider: 'url',
  253. data: {
  254. url: 'http://example.com/does-not-exist'
  255. },
  256. parent_folder_id: projectOneRootFolderId,
  257. name: 'url-test-file-3'
  258. }
  259. })
  260. expect(response.statusCode).to.equal(422) // unprocessable
  261. expect(body).to.equal(
  262. 'Your URL could not be reached (404 status code). Please check it and try again.'
  263. )
  264. // url is invalid
  265. ;({ response, body } = await owner.doRequest('post', {
  266. url: `/project/${projectOneId}/linked_file`,
  267. json: {
  268. provider: 'url',
  269. data: {
  270. url: '!^$%'
  271. },
  272. parent_folder_id: projectOneRootFolderId,
  273. name: 'url-test-file-4'
  274. }
  275. }))
  276. expect(response.statusCode).to.equal(422) // unprocessable
  277. expect(body).to.equal(
  278. 'Your URL is not valid. Please check it and try again.'
  279. )
  280. // URL is non-http
  281. ;({ response, body } = await owner.doRequest('post', {
  282. url: `/project/${projectOneId}/linked_file`,
  283. json: {
  284. provider: 'url',
  285. data: {
  286. url: 'ftp://localhost'
  287. },
  288. parent_folder_id: projectOneRootFolderId,
  289. name: 'url-test-file-5'
  290. }
  291. }))
  292. expect(response.statusCode).to.equal(422) // unprocessable
  293. expect(body).to.equal(
  294. 'Your URL is not valid. Please check it and try again.'
  295. )
  296. })
  297. it('should accept a URL withuot a leading http://, and add it', async function() {
  298. let { response, body } = await owner.doRequest('post', {
  299. url: `/project/${projectOneId}/linked_file`,
  300. json: {
  301. provider: 'url',
  302. data: {
  303. url: 'example.com/foo'
  304. },
  305. parent_folder_id: projectOneRootFolderId,
  306. name: 'url-test-file-6'
  307. }
  308. })
  309. expect(response.statusCode).to.equal(200)
  310. const updatedProject = await owner.getProject(projectOneId)
  311. const file = _.find(
  312. updatedProject.rootFolder[0].fileRefs,
  313. file => file.name === 'url-test-file-6'
  314. )
  315. expect(file.linkedFileData).to.deep.equal({
  316. provider: 'url',
  317. url: 'http://example.com/foo'
  318. })
  319. ;({ response, body } = await owner.doRequest(
  320. 'get',
  321. `/project/${projectOneId}/file/${file._id}`
  322. ))
  323. expect(response.statusCode).to.equal(200)
  324. expect(body).to.equal('foo foo foo')
  325. })
  326. })
  327. // TODO: Add test for asking for host that return ENOTFOUND
  328. // (This will probably end up handled by the proxy)
  329. describe('creating a linked output file', function() {
  330. beforeEach(async function() {
  331. projectOneId = await owner.createProject('output-test-one', {
  332. template: 'blank'
  333. })
  334. projectOne = await owner.getProject(projectOneId)
  335. projectOneRootFolderId = projectOne.rootFolder[0]._id.toString()
  336. projectTwoId = await owner.createProject('output-test-two', {
  337. template: 'blank'
  338. })
  339. projectTwo = await owner.getProject(projectTwoId)
  340. projectTwoRootFolderId = projectTwo.rootFolder[0]._id.toString()
  341. })
  342. it('should import the project.pdf file from the source project and refresh it', async function() {
  343. // import the file
  344. let { response, body } = await owner.doRequest('post', {
  345. url: `/project/${projectOneId}/linked_file`,
  346. json: {
  347. name: 'test.pdf',
  348. parent_folder_id: projectOneRootFolderId,
  349. provider: 'project_output_file',
  350. data: {
  351. source_project_id: projectTwoId,
  352. source_output_file_path: 'project.pdf',
  353. build_id: '1234-abcd'
  354. }
  355. }
  356. })
  357. expect(response.statusCode).to.equal(200)
  358. const existingFileId = body.new_file_id
  359. expect(existingFileId).to.exist
  360. const updatedProject = await owner.getProject(projectOneId)
  361. const firstFile = updatedProject.rootFolder[0].fileRefs[0]
  362. expect(firstFile._id.toString()).to.equal(existingFileId.toString())
  363. expect(firstFile.linkedFileData).to.deep.equal({
  364. provider: 'project_output_file',
  365. source_project_id: projectTwoId,
  366. source_output_file_path: 'project.pdf',
  367. build_id: '1234-abcd'
  368. })
  369. expect(firstFile.name).to.equal('test.pdf')
  370. // refresh the file
  371. ;({ response, body } = await owner.doRequest('post', {
  372. url: `/project/${projectOneId}/linked_file/${existingFileId}/refresh`,
  373. json: true
  374. }))
  375. expect(response.statusCode).to.equal(200)
  376. const refreshedFileId = body.new_file_id
  377. expect(refreshedFileId).to.exist
  378. expect(refreshedFileId).to.not.equal(existingFileId)
  379. const refreshedProject = await owner.getProject(projectOneId)
  380. const refreshedFile = refreshedProject.rootFolder[0].fileRefs[0]
  381. expect(refreshedFile._id.toString()).to.equal(refreshedFileId.toString())
  382. expect(refreshedFile.name).to.equal('test.pdf')
  383. })
  384. })
  385. describe('with a linked project_output_file from a v1 project that has not been imported', function() {
  386. beforeEach(async function() {
  387. projectOneId = await owner.createProject('output-v1-test-one', {
  388. template: 'blank'
  389. })
  390. projectOne = await owner.getProject(projectOneId)
  391. projectOneRootFolderId = projectOne.rootFolder[0]._id.toString()
  392. projectOne.rootFolder[0].fileRefs.push({
  393. linkedFileData: {
  394. provider: 'project_output_file',
  395. v1_source_doc_id: 9999999, // We won't find this id in the database
  396. source_output_file_path: 'project.pdf'
  397. },
  398. _id: 'abcdef',
  399. rev: 0,
  400. created: new Date(),
  401. name: 'whatever.pdf'
  402. })
  403. await owner.saveProject(projectOne)
  404. })
  405. it('should refuse to refresh', async function() {
  406. const { response, body } = await owner.doRequest('post', {
  407. url: `/project/${projectOneId}/linked_file/abcdef/refresh`,
  408. json: true
  409. })
  410. expect(response.statusCode).to.equal(409)
  411. expect(body).to.equal(
  412. 'Sorry, the source project is not yet imported to Overleaf v2. Please import it to Overleaf v2 to refresh this file'
  413. )
  414. })
  415. })
  416. })