DeletionTests.js 16 KB

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