DeletionTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. const User = require('./helpers/User')
  2. const request = require('./helpers/request')
  3. const async = require('async')
  4. const { expect } = require('chai')
  5. const settings = require('@overleaf/settings')
  6. const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb')
  7. const MockDocstoreApiClass = require('./mocks/MockDocstoreApi')
  8. const MockFilestoreApiClass = require('./mocks/MockFilestoreApi')
  9. const MockChatApiClass = require('./mocks/MockChatApi')
  10. let MockDocstoreApi, MockFilestoreApi, MockChatApi
  11. before(function () {
  12. MockDocstoreApi = MockDocstoreApiClass.instance()
  13. MockFilestoreApi = MockFilestoreApiClass.instance()
  14. MockChatApi = MockChatApiClass.instance()
  15. })
  16. describe('Deleting a user', function () {
  17. beforeEach(function (done) {
  18. this.user = new User()
  19. async.series(
  20. [
  21. this.user.ensureUserExists.bind(this.user),
  22. this.user.login.bind(this.user),
  23. ],
  24. done
  25. )
  26. })
  27. it('Should remove the user from active users', function (done) {
  28. this.user.get((error, user) => {
  29. expect(error).not.to.exist
  30. expect(user).to.exist
  31. this.user.deleteUser(error => {
  32. expect(error).not.to.exist
  33. this.user.get((error, user) => {
  34. expect(error).not.to.exist
  35. expect(user).not.to.exist
  36. done()
  37. })
  38. })
  39. })
  40. })
  41. it('Should create a soft-deleted user', function (done) {
  42. this.user.get((error, user) => {
  43. expect(error).not.to.exist
  44. this.user.deleteUser(error => {
  45. expect(error).not.to.exist
  46. db.deletedUsers.findOne(
  47. { 'user._id': user._id },
  48. (error, deletedUser) => {
  49. expect(error).not.to.exist
  50. expect(deletedUser).to.exist
  51. // it should set the 'deleterData' correctly
  52. expect(deletedUser.deleterData.deleterId.toString()).to.equal(
  53. user._id.toString()
  54. )
  55. expect(deletedUser.deleterData.deletedUserId.toString()).to.equal(
  56. user._id.toString()
  57. )
  58. expect(deletedUser.deleterData.deletedUserReferralId).to.equal(
  59. user.referal_id
  60. )
  61. // it should set the 'user' correctly
  62. expect(deletedUser.user._id.toString()).to.equal(
  63. user._id.toString()
  64. )
  65. expect(deletedUser.user.email).to.equal(user.email)
  66. done()
  67. }
  68. )
  69. })
  70. })
  71. })
  72. it("Should delete the user's projects", function (done) {
  73. this.user.createProject('wombat', (error, projectId) => {
  74. expect(error).not.to.exist
  75. this.user.getProject(projectId, (error, project) => {
  76. expect(error).not.to.exist
  77. expect(project).to.exist
  78. this.user.deleteUser(error => {
  79. expect(error).not.to.exist
  80. this.user.getProject(projectId, (error, project) => {
  81. expect(error).not.to.exist
  82. expect(project).not.to.exist
  83. done()
  84. })
  85. })
  86. })
  87. })
  88. })
  89. describe('when scrubbing the user', function () {
  90. beforeEach(function (done) {
  91. this.user.get((error, user) => {
  92. if (error) {
  93. throw error
  94. }
  95. this.userId = user._id
  96. this.user.deleteUser(done)
  97. })
  98. })
  99. it('Should remove the user data from mongo', function (done) {
  100. db.deletedUsers.findOne(
  101. { 'deleterData.deletedUserId': this.userId },
  102. (error, deletedUser) => {
  103. expect(error).not.to.exist
  104. expect(deletedUser).to.exist
  105. expect(deletedUser.deleterData.deleterIpAddress).to.exist
  106. expect(deletedUser.user).to.exist
  107. request.post(
  108. `/internal/users/${this.userId}/expire`,
  109. {
  110. auth: {
  111. user: settings.apis.web.user,
  112. pass: settings.apis.web.pass,
  113. sendImmediately: true,
  114. },
  115. },
  116. (error, res) => {
  117. expect(error).not.to.exist
  118. expect(res.statusCode).to.equal(204)
  119. db.deletedUsers.findOne(
  120. { 'deleterData.deletedUserId': this.userId },
  121. (error, deletedUser) => {
  122. expect(error).not.to.exist
  123. expect(deletedUser).to.exist
  124. expect(deletedUser.deleterData.deleterIpAddress).not.to.exist
  125. expect(deletedUser.user).not.to.exist
  126. done()
  127. }
  128. )
  129. }
  130. )
  131. }
  132. )
  133. })
  134. })
  135. })
  136. describe('Deleting a project', function () {
  137. beforeEach(function (done) {
  138. this.user = new User()
  139. this.projectName = 'wombat'
  140. this.user.ensureUserExists(() => {
  141. this.user.login(() => {
  142. this.user.createProject(this.projectName, (_e, projectId) => {
  143. this.projectId = projectId
  144. done()
  145. })
  146. })
  147. })
  148. })
  149. it('Should remove the project from active projects', function (done) {
  150. this.user.getProject(this.projectId, (error, project) => {
  151. expect(error).not.to.exist
  152. expect(project).to.exist
  153. this.user.deleteProject(this.projectId, error => {
  154. expect(error).not.to.exist
  155. this.user.getProject(this.projectId, (error, project) => {
  156. expect(error).not.to.exist
  157. expect(project).not.to.exist
  158. done()
  159. })
  160. })
  161. })
  162. })
  163. it('Should create a soft-deleted project', function (done) {
  164. this.user.getProject(this.projectId, (error, project) => {
  165. expect(error).not.to.exist
  166. this.user.get((error, user) => {
  167. expect(error).not.to.exist
  168. this.user.deleteProject(this.projectId, error => {
  169. expect(error).not.to.exist
  170. db.deletedProjects.findOne(
  171. { 'deleterData.deletedProjectId': project._id },
  172. (error, deletedProject) => {
  173. expect(error).not.to.exist
  174. expect(deletedProject).to.exist
  175. // it should set the 'deleterData' correctly
  176. expect(deletedProject.deleterData.deleterId.toString()).to.equal(
  177. user._id.toString()
  178. )
  179. expect(
  180. deletedProject.deleterData.deletedProjectId.toString()
  181. ).to.equal(project._id.toString())
  182. expect(
  183. deletedProject.deleterData.deletedProjectOwnerId.toString()
  184. ).to.equal(user._id.toString())
  185. // it should set the 'user' correctly
  186. expect(deletedProject.project._id.toString()).to.equal(
  187. project._id.toString()
  188. )
  189. expect(deletedProject.project.name).to.equal(this.projectName)
  190. done()
  191. }
  192. )
  193. })
  194. })
  195. })
  196. })
  197. describe('when the project has deleted files', function () {
  198. beforeEach('get rootFolder id', function (done) {
  199. this.user.getProject(this.projectId, (error, project) => {
  200. if (error) return done(error)
  201. this.rootFolder = project.rootFolder[0]._id
  202. done()
  203. })
  204. })
  205. let allFileIds
  206. beforeEach('reset allFileIds', function () {
  207. allFileIds = []
  208. })
  209. function createAndDeleteFile(name) {
  210. let fileId
  211. beforeEach(`create file ${name}`, function (done) {
  212. this.user.uploadExampleFileInProject(
  213. this.projectId,
  214. this.rootFolder,
  215. name,
  216. (error, theFileId) => {
  217. fileId = theFileId
  218. allFileIds.push(theFileId)
  219. done(error)
  220. }
  221. )
  222. })
  223. beforeEach(`delete file ${name}`, function (done) {
  224. this.user.deleteItemInProject(this.projectId, 'file', fileId, done)
  225. })
  226. }
  227. for (const name of ['a.png', 'another.png']) {
  228. createAndDeleteFile(name)
  229. }
  230. it('should have two deleteFiles entries', async function () {
  231. const files = await db.deletedFiles
  232. .find({}, { sort: { _id: 1 } })
  233. .toArray()
  234. expect(files).to.have.length(2)
  235. expect(files.map(file => file._id.toString())).to.deep.equal(allFileIds)
  236. })
  237. describe('When the deleted project is expired', function () {
  238. beforeEach('soft delete the project', function (done) {
  239. this.user.deleteProject(this.projectId, done)
  240. })
  241. beforeEach('hard delete the project', function (done) {
  242. request.post(
  243. `/internal/project/${this.projectId}/expire-deleted-project`,
  244. {
  245. auth: {
  246. user: settings.apis.web.user,
  247. pass: settings.apis.web.pass,
  248. sendImmediately: true,
  249. },
  250. },
  251. (error, res) => {
  252. expect(error).not.to.exist
  253. expect(res.statusCode).to.equal(200)
  254. done()
  255. }
  256. )
  257. })
  258. it('should cleanup the deleteFiles', async function () {
  259. const files = await db.deletedFiles
  260. .find({}, { sort: { _id: 1 } })
  261. .toArray()
  262. expect(files).to.deep.equal([])
  263. })
  264. })
  265. })
  266. describe('When the project has docs', function () {
  267. beforeEach(function (done) {
  268. this.user.getProject(this.projectId, (error, project) => {
  269. if (error) {
  270. throw error
  271. }
  272. this.user.createDocInProject(
  273. this.projectId,
  274. project.rootFolder[0]._id,
  275. 'potato',
  276. (error, docId) => {
  277. if (error) {
  278. throw error
  279. }
  280. this.docId = docId
  281. done()
  282. }
  283. )
  284. MockFilestoreApi.files[this.projectId.toString()] = {
  285. dummyFile: 'wombat',
  286. }
  287. MockChatApi.projects[this.projectId.toString()] = ['message']
  288. })
  289. })
  290. describe('When the deleted project is expired', function () {
  291. beforeEach(function (done) {
  292. this.user.deleteProject(this.projectId, error => {
  293. if (error) {
  294. throw error
  295. }
  296. done()
  297. })
  298. })
  299. it('Should destroy the docs', function (done) {
  300. expect(
  301. MockDocstoreApi.docs[this.projectId.toString()][this.docId.toString()]
  302. ).to.exist
  303. request.post(
  304. `/internal/project/${this.projectId}/expire-deleted-project`,
  305. {
  306. auth: {
  307. user: settings.apis.web.user,
  308. pass: settings.apis.web.pass,
  309. sendImmediately: true,
  310. },
  311. },
  312. (error, res) => {
  313. expect(error).not.to.exist
  314. expect(res.statusCode).to.equal(200)
  315. expect(MockDocstoreApi.docs[this.projectId.toString()]).not.to.exist
  316. done()
  317. }
  318. )
  319. })
  320. it('Should destroy the files', function (done) {
  321. expect(MockFilestoreApi.files[this.projectId.toString()]).to.exist
  322. request.post(
  323. `/internal/project/${this.projectId}/expire-deleted-project`,
  324. {
  325. auth: {
  326. user: settings.apis.web.user,
  327. pass: settings.apis.web.pass,
  328. sendImmediately: true,
  329. },
  330. },
  331. (error, res) => {
  332. expect(error).not.to.exist
  333. expect(res.statusCode).to.equal(200)
  334. expect(MockFilestoreApi.files[this.projectId.toString()]).not.to
  335. .exist
  336. done()
  337. }
  338. )
  339. })
  340. it('Should destroy the chat', function (done) {
  341. expect(MockChatApi.projects[this.projectId.toString()]).to.exist
  342. request.post(
  343. `/internal/project/${this.projectId}/expire-deleted-project`,
  344. {
  345. auth: {
  346. user: settings.apis.web.user,
  347. pass: settings.apis.web.pass,
  348. sendImmediately: true,
  349. },
  350. },
  351. (error, res) => {
  352. expect(error).not.to.exist
  353. expect(res.statusCode).to.equal(200)
  354. expect(MockChatApi.projects[this.projectId.toString()]).not.to.exist
  355. done()
  356. }
  357. )
  358. })
  359. it('Should remove the project data from mongo', function (done) {
  360. db.deletedProjects.findOne(
  361. { 'deleterData.deletedProjectId': ObjectId(this.projectId) },
  362. (error, deletedProject) => {
  363. expect(error).not.to.exist
  364. expect(deletedProject).to.exist
  365. expect(deletedProject.project).to.exist
  366. expect(deletedProject.deleterData.deleterIpAddress).to.exist
  367. expect(deletedProject.deleterData.deletedAt).to.exist
  368. request.post(
  369. `/internal/project/${this.projectId}/expire-deleted-project`,
  370. {
  371. auth: {
  372. user: settings.apis.web.user,
  373. pass: settings.apis.web.pass,
  374. sendImmediately: true,
  375. },
  376. },
  377. (error, res) => {
  378. expect(error).not.to.exist
  379. expect(res.statusCode).to.equal(200)
  380. db.deletedProjects.findOne(
  381. { 'deleterData.deletedProjectId': ObjectId(this.projectId) },
  382. (error, deletedProject) => {
  383. expect(error).not.to.exist
  384. expect(deletedProject).to.exist
  385. expect(deletedProject.project).not.to.exist
  386. expect(deletedProject.deleterData.deleterIpAddress).not.to
  387. .exist
  388. expect(deletedProject.deleterData.deletedAt).to.exist
  389. done()
  390. }
  391. )
  392. }
  393. )
  394. }
  395. )
  396. })
  397. })
  398. })
  399. })