ProjectStructureTests.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. const { expect } = require('chai')
  2. const { ObjectId } = require('mongodb')
  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.js')
  7. const User = require('./helpers/User')
  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(function (done) {
  18. owner = new User()
  19. owner.login(done)
  20. })
  21. function createExampleProject(owner, callback) {
  22. owner.createProject(
  23. 'example-project',
  24. { template: 'example' },
  25. (error, projectId) => {
  26. if (error) {
  27. return callback(error)
  28. }
  29. ProjectGetter.getProject(projectId, (error, project) => {
  30. if (error) {
  31. return callback(error)
  32. }
  33. const rootFolderId = project.rootFolder[0]._id.toString()
  34. callback(null, projectId, rootFolderId)
  35. })
  36. }
  37. )
  38. }
  39. function createExampleDoc(owner, projectId, callback) {
  40. ProjectGetter.getProject(projectId, (error, project) => {
  41. if (error) {
  42. return callback(error)
  43. }
  44. owner.request.post(
  45. {
  46. uri: `project/${projectId}/doc`,
  47. json: {
  48. name: 'new.tex',
  49. parent_folder_id: project.rootFolder[0]._id,
  50. },
  51. },
  52. (error, res, body) => {
  53. if (error) {
  54. return callback(error)
  55. }
  56. if (res.statusCode < 200 || res.statusCode >= 300) {
  57. return callback(new Error(`failed to add doc ${res.statusCode}`))
  58. }
  59. callback(null, body._id)
  60. }
  61. )
  62. })
  63. }
  64. function createExampleFolder(owner, projectId, callback) {
  65. owner.request.post(
  66. {
  67. uri: `project/${projectId}/folder`,
  68. json: {
  69. name: 'foo',
  70. },
  71. },
  72. (error, res, body) => {
  73. if (error) {
  74. return callback(error)
  75. }
  76. if (res.statusCode < 200 || res.statusCode >= 300) {
  77. return callback(new Error(`failed to add doc ${res.statusCode}`))
  78. }
  79. callback(null, body._id)
  80. }
  81. )
  82. }
  83. function uploadExampleProject(owner, zipFilename, options, callback) {
  84. if (typeof options === 'function') {
  85. callback = options
  86. options = {}
  87. }
  88. const zipFile = fs.createReadStream(
  89. Path.resolve(Path.join(__dirname, '..', 'files', zipFilename))
  90. )
  91. owner.request.post(
  92. {
  93. uri: 'project/new/upload',
  94. formData: {
  95. qqfile: zipFile,
  96. },
  97. },
  98. (error, res, body) => {
  99. if (error) {
  100. return callback(error)
  101. }
  102. if (
  103. !options.allowBadStatus &&
  104. (res.statusCode < 200 || res.statusCode >= 300)
  105. ) {
  106. return new Error(`failed to upload project ${res.statusCode}`)
  107. }
  108. callback(null, JSON.parse(body).project_id, res)
  109. }
  110. )
  111. }
  112. function deleteItem(owner, projectId, type, itemId, callback) {
  113. owner.deleteItemInProject(projectId, type, itemId, callback)
  114. }
  115. describe('uploading a project with a name', function () {
  116. let exampleProjectId
  117. const testProjectName = 'wombat'
  118. beforeEach(function (done) {
  119. uploadExampleProject(
  120. owner,
  121. 'test_project_with_name.zip',
  122. (err, projectId) => {
  123. if (err) {
  124. return done(err)
  125. }
  126. exampleProjectId = projectId
  127. done()
  128. }
  129. )
  130. })
  131. it('should set the project name from the zip contents', function (done) {
  132. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  133. expect(error).not.to.exist
  134. expect(project.name).to.equal(testProjectName)
  135. done()
  136. })
  137. })
  138. })
  139. describe('uploading a project with an invalid name', function () {
  140. let exampleProjectId
  141. const testProjectMatch = /^bad[^\\]+name$/
  142. beforeEach(function (done) {
  143. uploadExampleProject(
  144. owner,
  145. 'test_project_with_invalid_name.zip',
  146. (error, projectId) => {
  147. if (error) {
  148. return done(error)
  149. }
  150. exampleProjectId = projectId
  151. done()
  152. }
  153. )
  154. })
  155. it('should set the project name from the zip contents', function (done) {
  156. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  157. expect(error).not.to.exist
  158. expect(project.name).to.match(testProjectMatch)
  159. done()
  160. })
  161. })
  162. })
  163. describe('uploading an empty zipfile', function () {
  164. let res
  165. beforeEach(function (done) {
  166. uploadExampleProject(
  167. owner,
  168. 'test_project_empty.zip',
  169. { allowBadStatus: true },
  170. (err, projectId, response) => {
  171. if (err) {
  172. return done(err)
  173. }
  174. res = response
  175. done()
  176. }
  177. )
  178. })
  179. it('should fail with 422 error', function () {
  180. expect(res.statusCode).to.equal(422)
  181. })
  182. })
  183. describe('uploading a zipfile containing only empty directories', function () {
  184. let res
  185. beforeEach(function (done) {
  186. uploadExampleProject(
  187. owner,
  188. 'test_project_with_empty_folder.zip',
  189. { allowBadStatus: true },
  190. (err, projectId, response) => {
  191. if (err) {
  192. return done(err)
  193. }
  194. res = response
  195. done()
  196. }
  197. )
  198. })
  199. it('should fail with 422 error', function () {
  200. expect(res.statusCode).to.equal(422)
  201. })
  202. })
  203. describe('uploading a project with a shared top-level folder', function () {
  204. let exampleProjectId
  205. beforeEach(function (done) {
  206. uploadExampleProject(
  207. owner,
  208. 'test_project_with_shared_top_level_folder.zip',
  209. (err, projectId) => {
  210. if (err) {
  211. return done(err)
  212. }
  213. exampleProjectId = projectId
  214. done()
  215. }
  216. )
  217. })
  218. it('should not create the top-level folder', function (done) {
  219. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  220. expect(error).not.to.exist
  221. expect(project.rootFolder[0].folders.length).to.equal(0)
  222. expect(project.rootFolder[0].docs.length).to.equal(2)
  223. done()
  224. })
  225. })
  226. })
  227. describe('uploading a project with backslashes in the path names', function () {
  228. let exampleProjectId
  229. beforeEach(function (done) {
  230. uploadExampleProject(
  231. owner,
  232. 'test_project_with_backslash_in_filename.zip',
  233. (err, projectId) => {
  234. if (err) {
  235. return done(err)
  236. }
  237. exampleProjectId = projectId
  238. done()
  239. }
  240. )
  241. })
  242. it('should treat the backslash as a directory separator', function (done) {
  243. ProjectGetter.getProject(exampleProjectId, (error, project) => {
  244. expect(error).not.to.exist
  245. expect(project.rootFolder[0].folders[0].name).to.equal('styles')
  246. expect(project.rootFolder[0].folders[0].docs[0].name).to.equal('ao.sty')
  247. done()
  248. })
  249. })
  250. })
  251. describe('deleting docs', function () {
  252. beforeEach(function (done) {
  253. createExampleProject(owner, (err, projectId) => {
  254. if (err) {
  255. return done(err)
  256. }
  257. this.exampleProjectId = projectId
  258. createExampleFolder(owner, projectId, (err, folderId) => {
  259. if (err) {
  260. return done(err)
  261. }
  262. this.exampleFolderId = folderId
  263. createExampleDoc(owner, projectId, (err, docId) => {
  264. if (err) {
  265. return done(err)
  266. }
  267. this.exampleDocId = docId
  268. MockDocUpdaterApi.reset()
  269. ProjectGetter.getProject(
  270. this.exampleProjectId,
  271. (error, project) => {
  272. if (error) {
  273. throw error
  274. }
  275. this.project0 = project
  276. done()
  277. }
  278. )
  279. })
  280. })
  281. })
  282. })
  283. it('should pass the doc name to docstore', function (done) {
  284. deleteItem(
  285. owner,
  286. this.exampleProjectId,
  287. 'doc',
  288. this.exampleDocId,
  289. error => {
  290. if (error) return done(error)
  291. expect(
  292. MockDocStoreApi.getDeletedDocs(this.exampleProjectId)
  293. ).to.deep.equal([{ _id: this.exampleDocId, name: 'new.tex' }])
  294. done()
  295. }
  296. )
  297. })
  298. describe('when rootDoc_id matches doc being deleted', function () {
  299. beforeEach(function (done) {
  300. Project.updateOne(
  301. { _id: this.exampleProjectId },
  302. { $set: { rootDoc_id: this.exampleDocId } },
  303. done
  304. )
  305. })
  306. it('should clear rootDoc_id', function (done) {
  307. deleteItem(
  308. owner,
  309. this.exampleProjectId,
  310. 'doc',
  311. this.exampleDocId,
  312. () => {
  313. ProjectGetter.getProject(
  314. this.exampleProjectId,
  315. (error, project) => {
  316. if (error) {
  317. throw error
  318. }
  319. expect(project.rootDoc_id).to.be.undefined
  320. done()
  321. }
  322. )
  323. }
  324. )
  325. })
  326. })
  327. describe('when rootDoc_id does not match doc being deleted', function () {
  328. beforeEach(function (done) {
  329. this.exampleRootDocId = new ObjectId()
  330. Project.updateOne(
  331. { _id: this.exampleProjectId },
  332. { $set: { rootDoc_id: this.exampleRootDocId } },
  333. done
  334. )
  335. })
  336. it('should not clear rootDoc_id', function (done) {
  337. deleteItem(
  338. owner,
  339. this.exampleProjectId,
  340. 'doc',
  341. this.exampleDocId,
  342. () => {
  343. ProjectGetter.getProject(
  344. this.exampleProjectId,
  345. (error, project) => {
  346. if (error) {
  347. throw error
  348. }
  349. expect(project.rootDoc_id.toString()).to.equal(
  350. this.exampleRootDocId.toString()
  351. )
  352. done()
  353. }
  354. )
  355. }
  356. )
  357. })
  358. })
  359. })
  360. })