ProjectStructureTests.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. const { expect } = require('chai')
  2. const { ObjectId } = require('mongodb-legacy')
  3. const Path = require('path')
  4. const fs = require('fs')
  5. const { Project } = require('../../../app/src/models/Project')
  6. const ProjectGetter = require('../../../app/src/Features/Project/ProjectGetter')
  7. const User = require('./helpers/User').promises
  8. const MockDocStoreApiClass = require('./mocks/MockDocstoreApi')
  9. const MockDocUpdaterApiClass = require('./mocks/MockDocUpdaterApi')
  10. let MockDocStoreApi, MockDocUpdaterApi
  11. before(function () {
  12. MockDocUpdaterApi = MockDocUpdaterApiClass.instance()
  13. MockDocStoreApi = MockDocStoreApiClass.instance()
  14. })
  15. describe('ProjectStructureChanges', function () {
  16. let owner
  17. beforeEach(async function () {
  18. owner = new User()
  19. await owner.login()
  20. })
  21. async function createExampleProject(owner) {
  22. const projectId = await owner.createProject('example-project', {
  23. template: 'example',
  24. })
  25. const project = await ProjectGetter.promises.getProject(projectId)
  26. const rootFolderId = project.rootFolder[0]._id.toString()
  27. return { projectId, rootFolderId }
  28. }
  29. async function createExampleDoc(owner, projectId) {
  30. const project = await ProjectGetter.promises.getProject(projectId)
  31. const { response, body } = await owner.doRequest('POST', {
  32. uri: `project/${projectId}/doc`,
  33. json: {
  34. name: 'new.tex',
  35. parent_folder_id: project.rootFolder[0]._id,
  36. },
  37. })
  38. if (response.statusCode < 200 || response.statusCode >= 300) {
  39. throw new Error(`failed to add doc ${response.statusCode}`)
  40. }
  41. return body._id
  42. }
  43. async function createExampleFolder(owner, projectId) {
  44. const { response, body } = await owner.doRequest('POST', {
  45. uri: `project/${projectId}/folder`,
  46. json: {
  47. name: 'foo',
  48. },
  49. })
  50. if (response.statusCode < 200 || response.statusCode >= 300) {
  51. throw new Error(`failed to add doc ${response.statusCode}`)
  52. }
  53. return body._id
  54. }
  55. async function uploadExampleProject(owner, zipFilename, options = {}) {
  56. const zipFile = fs.createReadStream(
  57. Path.resolve(Path.join(__dirname, '..', 'files', zipFilename))
  58. )
  59. const { response, body } = await owner.doRequest('POST', {
  60. uri: 'project/new/upload',
  61. formData: {
  62. name: zipFilename,
  63. qqfile: zipFile,
  64. },
  65. })
  66. if (
  67. !options.allowBadStatus &&
  68. (response.statusCode < 200 || response.statusCode >= 300)
  69. ) {
  70. throw new Error(`failed to upload project ${response.statusCode}`)
  71. }
  72. return { projectId: JSON.parse(body).project_id, response }
  73. }
  74. async function deleteItem(owner, projectId, type, itemId) {
  75. return await owner.deleteItemInProject(projectId, type, itemId)
  76. }
  77. describe('uploading a project with a name', function () {
  78. let exampleProjectId
  79. const testProjectName = 'wombat'
  80. beforeEach(async function () {
  81. const { projectId } = await uploadExampleProject(
  82. owner,
  83. 'test_project_with_name.zip'
  84. )
  85. exampleProjectId = projectId
  86. })
  87. it('should set the project name from the zip contents', async function () {
  88. const project = await ProjectGetter.promises.getProject(exampleProjectId)
  89. expect(project.name).to.equal(testProjectName)
  90. })
  91. })
  92. describe('uploading a project with an invalid name', function () {
  93. let exampleProjectId
  94. const testProjectMatch = /^bad[^\\]+name$/
  95. beforeEach(async function () {
  96. const { projectId } = await uploadExampleProject(
  97. owner,
  98. 'test_project_with_invalid_name.zip'
  99. )
  100. exampleProjectId = projectId
  101. })
  102. it('should set the project name from the zip contents', async function () {
  103. const project = await ProjectGetter.promises.getProject(exampleProjectId)
  104. expect(project.name).to.match(testProjectMatch)
  105. })
  106. })
  107. describe('uploading an empty zipfile', function () {
  108. let res
  109. beforeEach(async function () {
  110. const { response } = await uploadExampleProject(
  111. owner,
  112. 'test_project_empty.zip',
  113. { allowBadStatus: true }
  114. )
  115. res = response
  116. })
  117. it('should fail with 422 error', function () {
  118. expect(res.statusCode).to.equal(422)
  119. })
  120. })
  121. describe('uploading a zipfile containing only empty directories', function () {
  122. let res
  123. beforeEach(async function () {
  124. const { response } = await uploadExampleProject(
  125. owner,
  126. 'test_project_with_empty_folder.zip',
  127. { allowBadStatus: true }
  128. )
  129. res = response
  130. })
  131. it('should fail with 422 error', function () {
  132. expect(res.statusCode).to.equal(422)
  133. })
  134. })
  135. describe('uploading a project with a shared top-level folder', function () {
  136. let exampleProjectId
  137. beforeEach(async function () {
  138. const { projectId } = await uploadExampleProject(
  139. owner,
  140. 'test_project_with_shared_top_level_folder.zip'
  141. )
  142. exampleProjectId = projectId
  143. })
  144. it('should not create the top-level folder', async function () {
  145. const project = await ProjectGetter.promises.getProject(exampleProjectId)
  146. expect(project.rootFolder[0].folders.length).to.equal(0)
  147. expect(project.rootFolder[0].docs.length).to.equal(2)
  148. })
  149. })
  150. describe('uploading a project with backslashes in the path names', function () {
  151. let exampleProjectId
  152. beforeEach(async function () {
  153. const { projectId } = await uploadExampleProject(
  154. owner,
  155. 'test_project_with_backslash_in_filename.zip'
  156. )
  157. exampleProjectId = projectId
  158. })
  159. it('should treat the backslash as a directory separator', async function () {
  160. const project = await ProjectGetter.promises.getProject(exampleProjectId)
  161. expect(project.rootFolder[0].folders[0].name).to.equal('styles')
  162. expect(project.rootFolder[0].folders[0].docs[0].name).to.equal('ao.sty')
  163. })
  164. })
  165. describe('deleting docs', function () {
  166. beforeEach(async function () {
  167. const { projectId } = await createExampleProject(owner)
  168. this.exampleProjectId = projectId
  169. const folderId = await createExampleFolder(owner, projectId)
  170. this.exampleFolderId = folderId
  171. const docId = await createExampleDoc(owner, projectId)
  172. this.exampleDocId = docId
  173. MockDocUpdaterApi.reset()
  174. const project = await ProjectGetter.promises.getProject(
  175. this.exampleProjectId
  176. )
  177. this.project0 = project
  178. })
  179. it('should pass the doc name to docstore', async function () {
  180. await deleteItem(owner, this.exampleProjectId, 'doc', this.exampleDocId)
  181. expect(
  182. MockDocStoreApi.getDeletedDocs(this.exampleProjectId)
  183. ).to.deep.equal([{ _id: this.exampleDocId, name: 'new.tex' }])
  184. })
  185. describe('when rootDoc_id matches doc being deleted', function () {
  186. beforeEach(async function () {
  187. await Project.updateOne(
  188. { _id: this.exampleProjectId },
  189. { $set: { rootDoc_id: this.exampleDocId } }
  190. ).exec()
  191. })
  192. it('should clear rootDoc_id', async function () {
  193. await deleteItem(owner, this.exampleProjectId, 'doc', this.exampleDocId)
  194. const project = ProjectGetter.promises.getProject(this.exampleProjectId)
  195. expect(project.rootDoc_id).to.be.undefined
  196. })
  197. })
  198. describe('when rootDoc_id does not match doc being deleted', function () {
  199. beforeEach(async function () {
  200. this.exampleRootDocId = new ObjectId()
  201. await Project.updateOne(
  202. { _id: this.exampleProjectId },
  203. { $set: { rootDoc_id: this.exampleRootDocId } }
  204. ).exec()
  205. })
  206. it('should not clear rootDoc_id', async function () {
  207. await deleteItem(owner, this.exampleProjectId, 'doc', this.exampleDocId)
  208. const project = await ProjectGetter.promises.getProject(
  209. this.exampleProjectId
  210. )
  211. expect(project.rootDoc_id.toString()).to.equal(
  212. this.exampleRootDocId.toString()
  213. )
  214. })
  215. })
  216. })
  217. })