LinkedFilesTests.js 15 KB

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