ProjectDuplicateNameTests.mjs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. import { expect } from 'chai'
  2. import sinon from 'sinon'
  3. import Path from 'node:path'
  4. import fs from 'node:fs'
  5. import _ from 'lodash'
  6. import User from './helpers/User.mjs'
  7. import UserHelper from './helpers/UserHelper.mjs'
  8. import MockDocstoreApiClass from './mocks/MockDocstoreApi.mjs'
  9. import MockFilestoreApiClass from './mocks/MockFilestoreApi.mjs'
  10. import MockV1HistoryApiClass from './mocks/MockV1HistoryApi.mjs'
  11. import { fileURLToPath } from 'node:url'
  12. import Features from '../../../app/src/infrastructure/Features.js'
  13. let MockDocstoreApi, MockFilestoreApi, MockV1HistoryApi
  14. const __dirname = fileURLToPath(new URL('.', import.meta.url))
  15. before(function () {
  16. MockDocstoreApi = MockDocstoreApiClass.instance()
  17. MockFilestoreApi = MockFilestoreApiClass.instance()
  18. MockV1HistoryApi = MockV1HistoryApiClass.instance()
  19. })
  20. describe('ProjectDuplicateNames', function () {
  21. beforeEach(function (done) {
  22. this.owner = new User()
  23. this.owner.login(done)
  24. this.project = {}
  25. this.callback = sinon.stub()
  26. })
  27. describe('creating a project from the example template', function () {
  28. beforeEach(function (done) {
  29. this.owner.createProject(
  30. 'example-project',
  31. { template: 'example' },
  32. (error, projectId) => {
  33. expect(error).to.not.exist
  34. this.example_project_id = projectId
  35. this.owner.getProject(projectId, (error, project) => {
  36. expect(error).to.not.exist
  37. this.project = project
  38. this.mainTexDoc = _.find(
  39. project.rootFolder[0].docs,
  40. doc => doc.name === 'main.tex'
  41. )
  42. this.refBibDoc = _.find(
  43. project.rootFolder[0].docs,
  44. doc => doc.name === 'sample.bib'
  45. )
  46. this.imageFile = _.find(
  47. project.rootFolder[0].fileRefs,
  48. file => file.name === 'frog.jpg'
  49. )
  50. this.rootFolderId = project.rootFolder[0]._id.toString()
  51. // create a folder called 'testfolder'
  52. this.owner.request.post(
  53. {
  54. uri: `/project/${this.example_project_id}/folder`,
  55. json: {
  56. name: 'testfolder',
  57. parent_folder_id: this.rootFolderId,
  58. },
  59. },
  60. (err, res, body) => {
  61. expect(err).to.not.exist
  62. this.testFolderId = body._id
  63. done()
  64. }
  65. )
  66. })
  67. }
  68. )
  69. })
  70. it('should create a project', function () {
  71. expect(this.project.rootFolder[0].docs.length).to.equal(2)
  72. expect(this.project.rootFolder[0].fileRefs.length).to.equal(1)
  73. })
  74. it('should create two docs in the docstore', function () {
  75. const docs = MockDocstoreApi.docs[this.example_project_id]
  76. expect(Object.keys(docs).length).to.equal(2)
  77. })
  78. if (Features.hasFeature('project-history-blobs')) {
  79. it('should create one file in the history-v1', function () {
  80. const files =
  81. MockV1HistoryApi.blobs[this.project.overleaf.history.id.toString()]
  82. expect(Object.keys(files).length).to.equal(1)
  83. })
  84. }
  85. if (Features.hasFeature('filestore')) {
  86. it('should create one file in the filestore', function () {
  87. const files = MockFilestoreApi.files[this.example_project_id]
  88. expect(Object.keys(files).length).to.equal(1)
  89. })
  90. }
  91. describe('for an existing doc', function () {
  92. describe('trying to add a doc with the same name', function () {
  93. beforeEach(function (done) {
  94. this.owner.request.post(
  95. {
  96. uri: `/project/${this.example_project_id}/doc`,
  97. json: {
  98. name: 'main.tex',
  99. parent_folder_id: this.rootFolderId,
  100. },
  101. },
  102. (err, res, body) => {
  103. expect(err).to.not.exist
  104. this.res = res
  105. done()
  106. }
  107. )
  108. })
  109. it('should respond with 400 error status', function () {
  110. expect(this.res.statusCode).to.equal(400)
  111. })
  112. })
  113. describe('trying to add a folder with the same name', function () {
  114. beforeEach(function (done) {
  115. this.owner.request.post(
  116. {
  117. uri: `/project/${this.example_project_id}/folder`,
  118. json: {
  119. name: 'main.tex',
  120. parent_folder_id: this.rootFolderId,
  121. },
  122. },
  123. (err, res, body) => {
  124. expect(err).to.not.exist
  125. this.res = res
  126. done()
  127. }
  128. )
  129. })
  130. it('should respond with 400 error status', function () {
  131. expect(this.res.statusCode).to.equal(400)
  132. })
  133. })
  134. })
  135. describe('for an existing file', function () {
  136. describe('trying to add a doc with the same name', function () {
  137. beforeEach(function (done) {
  138. this.owner.request.post(
  139. {
  140. uri: `/project/${this.example_project_id}/doc`,
  141. json: {
  142. name: 'frog.jpg',
  143. parent_folder_id: this.rootFolderId,
  144. },
  145. },
  146. (err, res, body) => {
  147. expect(err).to.not.exist
  148. this.res = res
  149. done()
  150. }
  151. )
  152. })
  153. it('should respond with 400 error status', function () {
  154. expect(this.res.statusCode).to.equal(400)
  155. })
  156. })
  157. describe('trying to add a folder with the same name', function () {
  158. beforeEach(function (done) {
  159. this.owner.request.post(
  160. {
  161. uri: `/project/${this.example_project_id}/folder`,
  162. json: {
  163. name: 'frog.jpg',
  164. parent_folder_id: this.rootFolderId,
  165. },
  166. },
  167. (err, res, body) => {
  168. expect(err).to.not.exist
  169. this.res = res
  170. done()
  171. }
  172. )
  173. })
  174. it('should respond with 400 error status', function () {
  175. expect(this.res.statusCode).to.equal(400)
  176. })
  177. })
  178. describe('trying to upload a file with the same name', function () {
  179. beforeEach(function (done) {
  180. this.owner.request.post(
  181. {
  182. uri: `/project/${this.example_project_id}/upload`,
  183. json: true,
  184. qs: {
  185. folder_id: this.rootFolderId,
  186. qqfilename: 'frog.jpg',
  187. },
  188. formData: {
  189. name: 'frog.jpg',
  190. qqfile: {
  191. value: fs.createReadStream(
  192. Path.join(__dirname, '/../files/1pixel.png')
  193. ),
  194. options: {
  195. filename: 'frog.jpg',
  196. contentType: 'image/jpeg',
  197. },
  198. },
  199. },
  200. },
  201. (err, res, body) => {
  202. expect(err).to.not.exist
  203. this.body = body
  204. // update the image id because we have replaced the file
  205. this.imageFile._id = this.body.entity_id
  206. done()
  207. }
  208. )
  209. })
  210. it('should succeed (overwriting the file)', function () {
  211. expect(this.body.success).to.equal(true)
  212. })
  213. })
  214. })
  215. // at this point the @imageFile._id has changed
  216. describe('for an existing folder', function () {
  217. describe('trying to add a doc with the same name', function () {
  218. beforeEach(function (done) {
  219. this.owner.request.post(
  220. {
  221. uri: `/project/${this.example_project_id}/doc`,
  222. json: {
  223. name: 'testfolder',
  224. parent_folder_id: this.rootFolderId,
  225. },
  226. },
  227. (err, res, body) => {
  228. expect(err).to.not.exist
  229. this.res = res
  230. done()
  231. }
  232. )
  233. })
  234. it('should respond with 400 error status', function () {
  235. expect(this.res.statusCode).to.equal(400)
  236. })
  237. })
  238. describe('trying to add a folder with the same name', function () {
  239. beforeEach(function (done) {
  240. this.owner.request.post(
  241. {
  242. uri: `/project/${this.example_project_id}/folder`,
  243. json: {
  244. name: 'testfolder',
  245. parent_folder_id: this.rootFolderId,
  246. },
  247. },
  248. (err, res, body) => {
  249. expect(err).to.not.exist
  250. this.res = res
  251. done()
  252. }
  253. )
  254. })
  255. it('should respond with 400 error status', function () {
  256. expect(this.res.statusCode).to.equal(400)
  257. })
  258. })
  259. describe('trying to upload a file with the same name', function () {
  260. beforeEach(function (done) {
  261. this.owner.request.post(
  262. {
  263. uri: `/project/${this.example_project_id}/upload`,
  264. json: true,
  265. qs: {
  266. folder_id: this.rootFolderId,
  267. qqfilename: 'frog.jpg',
  268. },
  269. formData: {
  270. qqfile: {
  271. value: fs.createReadStream(
  272. Path.join(__dirname, '/../files/1pixel.png')
  273. ),
  274. options: {
  275. filename: 'testfolder',
  276. contentType: 'image/jpeg',
  277. },
  278. },
  279. },
  280. },
  281. (err, res, body) => {
  282. expect(err).to.not.exist
  283. this.body = body
  284. done()
  285. }
  286. )
  287. })
  288. it('should respond with failure status', function () {
  289. expect(this.body.success).to.equal(false)
  290. })
  291. })
  292. })
  293. describe('rename for an existing doc', function () {
  294. describe('trying to rename a doc to the same name', function () {
  295. beforeEach(function (done) {
  296. this.owner.request.post(
  297. {
  298. uri: `/project/${this.example_project_id}/doc/${this.refBibDoc._id}/rename`,
  299. json: {
  300. name: 'main.tex',
  301. },
  302. },
  303. (err, res, body) => {
  304. expect(err).to.not.exist
  305. this.res = res
  306. done()
  307. }
  308. )
  309. })
  310. it('should respond with 400 error status', function () {
  311. expect(this.res.statusCode).to.equal(400)
  312. })
  313. })
  314. describe('trying to rename a folder to the same name', function () {
  315. beforeEach(function (done) {
  316. this.owner.request.post(
  317. {
  318. uri: `/project/${this.example_project_id}/folder/${this.testFolderId}/rename`,
  319. json: {
  320. name: 'main.tex',
  321. },
  322. },
  323. (err, res, body) => {
  324. expect(err).to.not.exist
  325. this.res = res
  326. done()
  327. }
  328. )
  329. })
  330. it('should respond with 400 error status', function () {
  331. expect(this.res.statusCode).to.equal(400)
  332. })
  333. })
  334. describe('trying to rename a file to the same name', function () {
  335. beforeEach(function (done) {
  336. this.owner.request.post(
  337. {
  338. uri: `/project/${this.example_project_id}/file/${this.imageFile._id}/rename`,
  339. json: {
  340. name: 'main.tex',
  341. },
  342. },
  343. (err, res, body) => {
  344. expect(err).to.not.exist
  345. this.res = res
  346. done()
  347. }
  348. )
  349. })
  350. it('should respond with failure status', function () {
  351. expect(this.res.statusCode).to.equal(400)
  352. })
  353. })
  354. })
  355. describe('rename for an existing file', function () {
  356. describe('trying to rename a doc to the same name', function () {
  357. beforeEach(function (done) {
  358. this.owner.request.post(
  359. {
  360. uri: `/project/${this.example_project_id}/doc/${this.refBibDoc._id}/rename`,
  361. json: {
  362. name: 'frog.jpg',
  363. },
  364. },
  365. (err, res, body) => {
  366. expect(err).to.not.exist
  367. this.res = res
  368. done()
  369. }
  370. )
  371. })
  372. it('should respond with 400 error status', function () {
  373. expect(this.res.statusCode).to.equal(400)
  374. })
  375. })
  376. describe('trying to rename a folder to the same name', function () {
  377. beforeEach(function (done) {
  378. this.owner.request.post(
  379. {
  380. uri: `/project/${this.example_project_id}/folder/${this.testFolderId}/rename`,
  381. json: {
  382. name: 'frog.jpg',
  383. },
  384. },
  385. (err, res, body) => {
  386. expect(err).to.not.exist
  387. this.res = res
  388. done()
  389. }
  390. )
  391. })
  392. it('should respond with 400 error status', function () {
  393. expect(this.res.statusCode).to.equal(400)
  394. })
  395. })
  396. describe('trying to rename a file to the same name', function () {
  397. beforeEach(function (done) {
  398. this.owner.request.post(
  399. {
  400. uri: `/project/${this.example_project_id}/file/${this.imageFile._id}/rename`,
  401. json: {
  402. name: 'frog.jpg',
  403. },
  404. },
  405. (err, res, body) => {
  406. expect(err).to.not.exist
  407. this.res = res
  408. done()
  409. }
  410. )
  411. })
  412. it('should respond with failure status', function () {
  413. expect(this.res.statusCode).to.equal(400)
  414. })
  415. })
  416. })
  417. describe('rename for an existing folder', function () {
  418. describe('trying to rename a doc to the same name', function () {
  419. beforeEach(function (done) {
  420. this.owner.request.post(
  421. {
  422. uri: `/project/${this.example_project_id}/doc/${this.refBibDoc._id}/rename`,
  423. json: {
  424. name: 'testfolder',
  425. },
  426. },
  427. (err, res, body) => {
  428. expect(err).to.not.exist
  429. this.res = res
  430. done()
  431. }
  432. )
  433. })
  434. it('should respond with 400 error status', function () {
  435. expect(this.res.statusCode).to.equal(400)
  436. })
  437. })
  438. describe('trying to rename a folder to the same name', function () {
  439. beforeEach(function (done) {
  440. this.owner.request.post(
  441. {
  442. uri: `/project/${this.example_project_id}/folder/${this.testFolderId}/rename`,
  443. json: {
  444. name: 'testfolder',
  445. },
  446. },
  447. (err, res, body) => {
  448. expect(err).to.not.exist
  449. this.res = res
  450. done()
  451. }
  452. )
  453. })
  454. it('should respond with 400 error status', function () {
  455. expect(this.res.statusCode).to.equal(400)
  456. })
  457. })
  458. describe('trying to rename a file to the same name', function () {
  459. beforeEach(function (done) {
  460. this.owner.request.post(
  461. {
  462. uri: `/project/${this.example_project_id}/file/${this.imageFile._id}/rename`,
  463. json: {
  464. name: 'testfolder',
  465. },
  466. },
  467. (err, res, body) => {
  468. expect(err).to.not.exist
  469. this.res = res
  470. done()
  471. }
  472. )
  473. })
  474. it('should respond with failure status', function () {
  475. expect(this.res.statusCode).to.equal(400)
  476. })
  477. })
  478. })
  479. describe('for an existing folder with a file with the same name', function () {
  480. beforeEach(function (done) {
  481. this.owner.request.post(
  482. {
  483. uri: `/project/${this.example_project_id}/doc`,
  484. json: {
  485. name: 'main.tex',
  486. parent_folder_id: this.testFolderId,
  487. },
  488. },
  489. (err, res, body) => {
  490. if (err) {
  491. throw err
  492. }
  493. this.owner.request.post(
  494. {
  495. uri: `/project/${this.example_project_id}/doc`,
  496. json: {
  497. name: 'frog.jpg',
  498. parent_folder_id: this.testFolderId,
  499. },
  500. },
  501. (err, res, body) => {
  502. if (err) {
  503. throw err
  504. }
  505. this.owner.request.post(
  506. {
  507. uri: `/project/${this.example_project_id}/folder`,
  508. json: {
  509. name: 'otherFolder',
  510. parent_folder_id: this.testFolderId,
  511. },
  512. },
  513. (err, res, body) => {
  514. if (err) {
  515. throw err
  516. }
  517. this.subFolderId = body._id
  518. this.owner.request.post(
  519. {
  520. uri: `/project/${this.example_project_id}/folder`,
  521. json: {
  522. name: 'otherFolder',
  523. parent_folder_id: this.rootFolderId,
  524. },
  525. },
  526. (err, res, body) => {
  527. if (err) {
  528. throw err
  529. }
  530. this.otherFolderId = body._id
  531. done()
  532. }
  533. )
  534. }
  535. )
  536. }
  537. )
  538. }
  539. )
  540. })
  541. describe('trying to move a doc into the folder', function () {
  542. beforeEach(function (done) {
  543. this.owner.request.post(
  544. {
  545. uri: `/project/${this.example_project_id}/doc/${this.mainTexDoc._id}/move`,
  546. json: {
  547. folder_id: this.testFolderId,
  548. },
  549. },
  550. (err, res, body) => {
  551. expect(err).to.not.exist
  552. this.res = res
  553. done()
  554. }
  555. )
  556. })
  557. it('should respond with 400 error status', function () {
  558. expect(this.res.statusCode).to.equal(400)
  559. })
  560. })
  561. describe('trying to move a file into the folder', function () {
  562. beforeEach(function (done) {
  563. this.owner.request.post(
  564. {
  565. uri: `/project/${this.example_project_id}/file/${this.imageFile._id}/move`,
  566. json: {
  567. folder_id: this.testFolderId,
  568. },
  569. },
  570. (err, res, body) => {
  571. expect(err).to.not.exist
  572. this.res = res
  573. done()
  574. }
  575. )
  576. })
  577. it('should respond with 400 error status', function () {
  578. expect(this.res.statusCode).to.equal(400)
  579. })
  580. })
  581. describe('trying to move a folder into the folder', function () {
  582. beforeEach(function (done) {
  583. this.owner.request.post(
  584. {
  585. uri: `/project/${this.example_project_id}/folder/${this.otherFolderId}/move`,
  586. json: {
  587. folder_id: this.testFolderId,
  588. },
  589. },
  590. (err, res, body) => {
  591. expect(err).to.not.exist
  592. this.res = res
  593. done()
  594. }
  595. )
  596. })
  597. it('should respond with 400 error status', function () {
  598. expect(this.res.statusCode).to.equal(400)
  599. })
  600. })
  601. describe('trying to move a folder into a subfolder of itself', function () {
  602. beforeEach(function (done) {
  603. this.owner.request.post(
  604. {
  605. uri: `/project/${this.example_project_id}/folder/${this.testFolderId}/move`,
  606. json: {
  607. folder_id: this.subFolderId,
  608. },
  609. },
  610. (err, res, body) => {
  611. expect(err).to.not.exist
  612. this.res = res
  613. done()
  614. }
  615. )
  616. })
  617. it('should respond with 400 error status', function () {
  618. expect(this.res.statusCode).to.equal(400)
  619. })
  620. })
  621. })
  622. })
  623. describe('regex characters in title', function () {
  624. let response, userHelper
  625. beforeEach(async function () {
  626. userHelper = new UserHelper()
  627. userHelper = await UserHelper.createUser()
  628. userHelper = await UserHelper.loginUser(
  629. userHelper.getDefaultEmailPassword()
  630. )
  631. })
  632. it('should handle characters that would cause an invalid regular expression', async function () {
  633. const projectName = 'Example (test'
  634. response = await userHelper.fetch('/project/new', {
  635. method: 'POST',
  636. body: new URLSearchParams([['projectName', projectName]]),
  637. })
  638. const body = await response.json()
  639. expect(response.status).to.equal(200) // can create project
  640. response = await userHelper.fetch(`/project/${body.project_id}`)
  641. expect(response.status).to.equal(200) // can open project
  642. })
  643. })
  644. })