DeletionTests.js 12 KB

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