UserDeleter.test.mjs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. import { beforeEach, describe, expect, vi, it } from 'vitest'
  2. import sinon from 'sinon'
  3. import tk from 'timekeeper'
  4. import moment from 'moment'
  5. import Errors from '../../../../app/src/Features/Errors/Errors.js'
  6. import mongoose from 'mongoose'
  7. import { DeletedUser } from '../../../../app/src/models/DeletedUser.mjs'
  8. import { User } from '../../../../app/src/models/User.mjs'
  9. const ObjectId = mongoose.Types.ObjectId
  10. vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
  11. vi.importActual('../../../../app/src/Features/Errors/Errors.js')
  12. )
  13. const modulePath = '../../../../app/src/Features/User/UserDeleter.mjs'
  14. describe('UserDeleter', function () {
  15. beforeEach(async function (ctx) {
  16. ctx.userId = new ObjectId()
  17. ctx.ipAddress = '1.2.3.4'
  18. ctx.UserMock = sinon.mock(User)
  19. ctx.DeletedUserMock = sinon.mock(DeletedUser)
  20. tk.freeze(Date.now())
  21. ctx.mockedUser = sinon.mock(
  22. new User({
  23. _id: ctx.userId,
  24. email: 'bob@bob.com',
  25. lastLoggedIn: Date.now() + 1000,
  26. signUpDate: Date.now() + 2000,
  27. loginCount: 10,
  28. overleaf: {
  29. id: 1234,
  30. },
  31. refered_users: ['wombat', 'potato'],
  32. refered_user_count: 2,
  33. referal_id: ['giraffe'],
  34. })
  35. )
  36. ctx.user = ctx.mockedUser.object
  37. ctx.ProjectDeleter = {
  38. promises: {
  39. deleteUsersProjects: sinon.stub().resolves(),
  40. },
  41. }
  42. ctx.SubscriptionHandler = {
  43. promises: {
  44. cancelSubscription: sinon.stub().resolves(),
  45. },
  46. }
  47. ctx.SubscriptionUpdater = {
  48. promises: {
  49. removeUserFromAllGroups: sinon.stub().resolves(),
  50. },
  51. }
  52. ctx.SubscriptionLocator = {
  53. promises: {
  54. getUsersSubscription: sinon.stub().resolves(),
  55. getUniqueManagedSubscriptionMemberOf: sinon.stub().resolves(),
  56. },
  57. }
  58. ctx.UserMembershipsHandler = {
  59. promises: {
  60. removeUserFromAllEntities: sinon.stub().resolves(),
  61. },
  62. }
  63. ctx.UserSessionsManager = {
  64. promises: {
  65. removeSessionsFromRedis: sinon.stub().resolves(),
  66. },
  67. }
  68. ctx.InstitutionsApi = {
  69. promises: {
  70. deleteAffiliations: sinon.stub().resolves(),
  71. },
  72. }
  73. ctx.UserAuditLogEntry = {
  74. deleteMany: sinon.stub().returns({ exec: sinon.stub().resolves() }),
  75. }
  76. ctx.Modules = {
  77. promises: { hooks: { fire: sinon.stub().resolves() } },
  78. }
  79. ctx.OnboardingDataCollectionManager = {
  80. deleteOnboardingDataCollection: sinon.stub().resolves(),
  81. }
  82. ctx.EmailHandler = {
  83. promises: {
  84. sendEmail: sinon.stub().resolves(),
  85. },
  86. }
  87. ctx.UserAuditLogHandler = {
  88. promises: {
  89. addEntry: sinon.stub().resolves(),
  90. },
  91. }
  92. vi.doMock('../../../../app/src/models/User', () => ({
  93. User,
  94. }))
  95. vi.doMock('../../../../app/src/models/DeletedUser', () => ({
  96. DeletedUser,
  97. }))
  98. vi.doMock('../../../../app/src/Features/User/UserSessionsManager', () => ({
  99. default: ctx.UserSessionsManager,
  100. }))
  101. vi.doMock(
  102. '../../../../app/src/Features/Subscription/SubscriptionHandler',
  103. () => ({
  104. default: ctx.SubscriptionHandler,
  105. })
  106. )
  107. vi.doMock(
  108. '../../../../app/src/Features/Subscription/SubscriptionUpdater',
  109. () => ({
  110. default: ctx.SubscriptionUpdater,
  111. })
  112. )
  113. vi.doMock(
  114. '../../../../app/src/Features/Subscription/SubscriptionLocator',
  115. () => ({
  116. default: ctx.SubscriptionLocator,
  117. })
  118. )
  119. vi.doMock(
  120. '../../../../app/src/Features/UserMembership/UserMembershipsHandler',
  121. () => ({
  122. default: ctx.UserMembershipsHandler,
  123. })
  124. )
  125. vi.doMock('../../../../app/src/Features/Project/ProjectDeleter', () => ({
  126. default: ctx.ProjectDeleter,
  127. }))
  128. vi.doMock(
  129. '../../../../app/src/Features/Institutions/InstitutionsAPI',
  130. () => ({
  131. default: ctx.InstitutionsApi,
  132. })
  133. )
  134. vi.doMock('../../../../app/src/models/UserAuditLogEntry', () => ({
  135. UserAuditLogEntry: ctx.UserAuditLogEntry,
  136. }))
  137. vi.doMock('../../../../app/src/Features/User/UserAuditLogHandler', () => ({
  138. default: ctx.UserAuditLogHandler,
  139. }))
  140. vi.doMock('../../../../app/src/infrastructure/Modules', () => ({
  141. default: ctx.Modules,
  142. }))
  143. vi.doMock(
  144. '../../../../app/src/Features/OnboardingDataCollection/OnboardingDataCollectionManager',
  145. () => ({
  146. default: ctx.OnboardingDataCollectionManager,
  147. })
  148. )
  149. vi.doMock('../../../../app/src/Features/Email/EmailHandler', () => ({
  150. default: ctx.EmailHandler,
  151. }))
  152. ctx.UserDeleter = (await import(modulePath)).default
  153. })
  154. afterEach(function (ctx) {
  155. ctx.DeletedUserMock.restore()
  156. ctx.UserMock.restore()
  157. ctx.mockedUser.restore()
  158. tk.reset()
  159. })
  160. describe('deleteUser', function () {
  161. beforeEach(function (ctx) {
  162. ctx.UserMock.expects('findById')
  163. .withArgs(ctx.userId)
  164. .chain('exec')
  165. .resolves(ctx.user)
  166. })
  167. describe('when the user can be deleted', function () {
  168. beforeEach(function (ctx) {
  169. ctx.deletedUser = {
  170. user: ctx.user,
  171. deleterData: {
  172. deletedAt: new Date(),
  173. deletedUserId: ctx.userId,
  174. deleterIpAddress: ctx.ipAddress,
  175. deleterId: undefined,
  176. deletedUserLastLoggedIn: ctx.user.lastLoggedIn,
  177. deletedUserSignUpDate: ctx.user.signUpDate,
  178. deletedUserLoginCount: ctx.user.loginCount,
  179. deletedUserReferralId: ctx.user.referal_id,
  180. deletedUserReferredUsers: ctx.user.refered_users,
  181. deletedUserReferredUserCount: ctx.user.refered_user_count,
  182. deletedUserOverleafId: ctx.user.overleaf.id,
  183. },
  184. }
  185. })
  186. describe('when only the ip address is passed', function () {
  187. beforeEach(function (ctx) {
  188. ctx.DeletedUserMock.expects('updateOne')
  189. .withArgs(
  190. { 'deleterData.deletedUserId': ctx.userId },
  191. ctx.deletedUser,
  192. { upsert: true }
  193. )
  194. .chain('exec')
  195. .resolves()
  196. })
  197. describe('when unsubscribing in Mailchimp succeeds', function () {
  198. beforeEach(function (ctx) {
  199. ctx.UserMock.expects('deleteOne')
  200. .withArgs({ _id: ctx.userId })
  201. .chain('exec')
  202. .resolves()
  203. })
  204. it('should find and the user in mongo by its id', async function (ctx) {
  205. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  206. ipAddress: ctx.ipAddress,
  207. })
  208. ctx.UserMock.verify()
  209. })
  210. it('should delete all the projects of a user', async function (ctx) {
  211. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  212. ipAddress: ctx.ipAddress,
  213. })
  214. expect(
  215. ctx.ProjectDeleter.promises.deleteUsersProjects
  216. ).to.have.been.calledWith(ctx.userId)
  217. })
  218. it("should cancel the user's subscription", async function (ctx) {
  219. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  220. ipAddress: ctx.ipAddress,
  221. })
  222. expect(
  223. ctx.SubscriptionHandler.promises.cancelSubscription
  224. ).to.have.been.calledWith(ctx.user)
  225. })
  226. it('should delete user affiliations', async function (ctx) {
  227. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  228. ipAddress: ctx.ipAddress,
  229. })
  230. expect(
  231. ctx.InstitutionsApi.promises.deleteAffiliations
  232. ).to.have.been.calledWith(ctx.userId)
  233. })
  234. it('should cleanup collabratec access tokens', async function (ctx) {
  235. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  236. ipAddress: ctx.ipAddress,
  237. })
  238. expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
  239. 'cleanupPersonalAccessTokens',
  240. ctx.userId,
  241. ['collabratec', 'git_bridge']
  242. )
  243. })
  244. it('should fire the deleteUser hook for modules', async function (ctx) {
  245. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  246. ipAddress: ctx.ipAddress,
  247. })
  248. expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
  249. 'deleteUser',
  250. ctx.userId
  251. )
  252. })
  253. it('should stop the user sessions', async function (ctx) {
  254. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  255. ipAddress: ctx.ipAddress,
  256. })
  257. expect(
  258. ctx.UserSessionsManager.promises.removeSessionsFromRedis
  259. ).to.have.been.calledWith(ctx.user)
  260. })
  261. it('should remove user from group subscriptions', async function (ctx) {
  262. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  263. ipAddress: ctx.ipAddress,
  264. })
  265. expect(
  266. ctx.SubscriptionUpdater.promises.removeUserFromAllGroups
  267. ).to.have.been.calledWith(ctx.userId)
  268. })
  269. it('should remove user memberships', async function (ctx) {
  270. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  271. ipAddress: ctx.ipAddress,
  272. })
  273. expect(
  274. ctx.UserMembershipsHandler.promises.removeUserFromAllEntities
  275. ).to.have.been.calledWith(ctx.userId)
  276. })
  277. it('rejects if the user is a subscription admin', async function (ctx) {
  278. ctx.SubscriptionLocator.promises.getUsersSubscription.rejects({
  279. _id: 'some-subscription',
  280. })
  281. await expect(ctx.UserDeleter.promises.deleteUser(ctx.userId, {})).to
  282. .be.rejected
  283. })
  284. it('should create a deletedUser', async function (ctx) {
  285. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  286. ipAddress: ctx.ipAddress,
  287. })
  288. ctx.DeletedUserMock.verify()
  289. })
  290. describe('email notifications', function () {
  291. it('should email the user', async function (ctx) {
  292. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  293. ipAddress: ctx.ipAddress,
  294. })
  295. const emailOptions = {
  296. to: 'bob@bob.com',
  297. action: 'account deleted',
  298. actionDescribed: 'your Overleaf account was deleted',
  299. }
  300. expect(
  301. ctx.EmailHandler.promises.sendEmail
  302. ).to.have.been.calledWith('securityAlert', emailOptions)
  303. })
  304. it('should not email the user with skipEmail === true', async function (ctx) {
  305. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  306. ipAddress: ctx.ipAddress,
  307. skipEmail: true,
  308. })
  309. expect(ctx.EmailHandler.promises.sendEmail).not.to.have.been
  310. .called
  311. })
  312. it('should fail when the email service fails', async function (ctx) {
  313. ctx.EmailHandler.promises.sendEmail = sinon
  314. .stub()
  315. .rejects(new Error('email failed'))
  316. await expect(
  317. ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  318. ipAddress: ctx.ipAddress,
  319. })
  320. ).to.be.rejectedWith('email failed')
  321. })
  322. describe('with "force: true" option', function () {
  323. it('should succeed when the email service fails', async function (ctx) {
  324. ctx.EmailHandler.promises.sendEmail = sinon
  325. .stub()
  326. .rejects(new Error('email failed'))
  327. await expect(
  328. ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  329. ipAddress: ctx.ipAddress,
  330. force: true,
  331. })
  332. ).to.be.fulfilled
  333. })
  334. })
  335. })
  336. it('should add an audit log entry', async function (ctx) {
  337. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  338. ipAddress: ctx.ipAddress,
  339. })
  340. expect(
  341. ctx.UserAuditLogHandler.promises.addEntry
  342. ).to.have.been.calledWith(
  343. ctx.userId,
  344. 'delete-account',
  345. ctx.userId,
  346. ctx.ipAddress,
  347. {}
  348. )
  349. })
  350. })
  351. describe('when called as a callback', function () {
  352. beforeEach(function (ctx) {
  353. ctx.UserMock.expects('deleteOne')
  354. .withArgs({ _id: ctx.userId })
  355. .chain('exec')
  356. .resolves()
  357. })
  358. it('should delete the user', async function (ctx) {
  359. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  360. ipAddress: ctx.ipAddress,
  361. })
  362. ctx.UserMock.verify()
  363. ctx.DeletedUserMock.verify()
  364. })
  365. })
  366. })
  367. describe('when a user and IP address are specified', function () {
  368. beforeEach(function (ctx) {
  369. ctx.ipAddress = '1.2.3.4'
  370. ctx.deleterId = new ObjectId()
  371. ctx.deletedUser.deleterData.deleterIpAddress = ctx.ipAddress
  372. ctx.deletedUser.deleterData.deleterId = ctx.deleterId
  373. ctx.DeletedUserMock.expects('updateOne')
  374. .withArgs(
  375. { 'deleterData.deletedUserId': ctx.userId },
  376. ctx.deletedUser,
  377. { upsert: true }
  378. )
  379. .chain('exec')
  380. .resolves()
  381. ctx.UserMock.expects('deleteOne')
  382. .withArgs({ _id: ctx.userId })
  383. .chain('exec')
  384. .resolves()
  385. })
  386. it('should add the deleted user id and ip address to the deletedUser', async function (ctx) {
  387. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  388. deleterUser: { _id: ctx.deleterId },
  389. ipAddress: ctx.ipAddress,
  390. })
  391. ctx.DeletedUserMock.verify()
  392. })
  393. it('should add an audit log entry', async function (ctx) {
  394. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  395. deleterUser: { _id: ctx.deleterId },
  396. ipAddress: ctx.ipAddress,
  397. })
  398. expect(
  399. ctx.UserAuditLogHandler.promises.addEntry
  400. ).to.have.been.calledWith(
  401. ctx.userId,
  402. 'delete-account',
  403. ctx.deleterId,
  404. ctx.ipAddress,
  405. {}
  406. )
  407. })
  408. describe('when called as a callback', function () {
  409. it('should delete the user', async function (ctx) {
  410. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  411. deleterUser: { _id: ctx.deleterId },
  412. ipAddress: ctx.ipAddress,
  413. })
  414. ctx.UserMock.verify()
  415. ctx.DeletedUserMock.verify()
  416. })
  417. })
  418. })
  419. describe('when the user is part of a managed subscription', function () {
  420. beforeEach(function (ctx) {
  421. ctx.managedSubscriptionId = new ObjectId()
  422. ctx.SubscriptionLocator.promises.getUniqueManagedSubscriptionMemberOf.resolves(
  423. {
  424. _id: ctx.managedSubscriptionId,
  425. }
  426. )
  427. ctx.DeletedUserMock.expects('updateOne')
  428. .withArgs(
  429. { 'deleterData.deletedUserId': ctx.userId },
  430. ctx.deletedUser,
  431. { upsert: true }
  432. )
  433. .chain('exec')
  434. .resolves()
  435. ctx.UserMock.expects('deleteOne')
  436. .withArgs({ _id: ctx.userId })
  437. .chain('exec')
  438. .resolves()
  439. })
  440. it('should include managedSubscriptionId in audit log', async function (ctx) {
  441. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  442. ipAddress: ctx.ipAddress,
  443. })
  444. expect(
  445. ctx.UserAuditLogHandler.promises.addEntry
  446. ).to.have.been.calledWith(
  447. ctx.userId,
  448. 'delete-account',
  449. ctx.userId,
  450. ctx.ipAddress,
  451. {}
  452. )
  453. })
  454. })
  455. describe('when checking managed subscription fails', function () {
  456. beforeEach(function (ctx) {
  457. ctx.SubscriptionLocator.promises.getUniqueManagedSubscriptionMemberOf.rejects(
  458. new Error('subscription lookup failed')
  459. )
  460. ctx.DeletedUserMock.expects('updateOne')
  461. .withArgs(
  462. { 'deleterData.deletedUserId': ctx.userId },
  463. ctx.deletedUser,
  464. { upsert: true }
  465. )
  466. .chain('exec')
  467. .resolves()
  468. ctx.UserMock.expects('deleteOne')
  469. .withArgs({ _id: ctx.userId })
  470. .chain('exec')
  471. .resolves()
  472. })
  473. it('should continue with deletion and not include managedSubscriptionId', async function (ctx) {
  474. await ctx.UserDeleter.promises.deleteUser(ctx.userId, {
  475. ipAddress: ctx.ipAddress,
  476. })
  477. expect(
  478. ctx.UserAuditLogHandler.promises.addEntry
  479. ).to.have.been.calledWith(
  480. ctx.userId,
  481. 'delete-account',
  482. ctx.userId,
  483. ctx.ipAddress,
  484. {}
  485. )
  486. })
  487. })
  488. })
  489. describe('when the user cannot be deleted because they are a subscription admin', function () {
  490. beforeEach(function (ctx) {
  491. ctx.SubscriptionLocator.promises.getUsersSubscription.resolves({
  492. _id: 'some-subscription',
  493. })
  494. })
  495. it('fails with a SubscriptionAdminDeletionError', async function (ctx) {
  496. await expect(
  497. ctx.UserDeleter.promises.deleteUser(ctx.userId)
  498. ).to.be.rejectedWith(Errors.SubscriptionAdminDeletionError)
  499. })
  500. it('should not create a deletedUser', async function (ctx) {
  501. await expect(ctx.UserDeleter.promises.deleteUser(ctx.userId)).to.be
  502. .rejected
  503. ctx.DeletedUserMock.verify()
  504. })
  505. it('should not remove the user from mongo', async function (ctx) {
  506. await expect(ctx.UserDeleter.promises.deleteUser(ctx.userId)).to.be
  507. .rejected
  508. ctx.UserMock.verify()
  509. })
  510. })
  511. })
  512. describe('ensureCanDeleteUser', function () {
  513. it('should not return error when user can be deleted', async function (ctx) {
  514. ctx.SubscriptionLocator.promises.getUsersSubscription.resolves(null)
  515. let error
  516. try {
  517. await ctx.UserDeleter.promises.ensureCanDeleteUser(ctx.user)
  518. } catch (e) {
  519. error = e
  520. } finally {
  521. expect(error).not.to.exist
  522. }
  523. })
  524. it('should return custom error when user is group admin', async function (ctx) {
  525. ctx.SubscriptionLocator.promises.getUsersSubscription.resolves({
  526. _id: '123abc',
  527. })
  528. let error
  529. try {
  530. await ctx.UserDeleter.promises.ensureCanDeleteUser(ctx.user)
  531. } catch (e) {
  532. error = e
  533. } finally {
  534. expect(error).to.be.instanceof(Errors.SubscriptionAdminDeletionError)
  535. }
  536. })
  537. it('propagates errors', async function (ctx) {
  538. ctx.SubscriptionLocator.promises.getUsersSubscription.rejects(
  539. new Error('Some error')
  540. )
  541. let error
  542. try {
  543. await ctx.UserDeleter.promises.ensureCanDeleteUser(ctx.user)
  544. } catch (e) {
  545. error = e
  546. } finally {
  547. expect(error).to.be.instanceof(Error)
  548. }
  549. })
  550. })
  551. describe('expireDeletedUsersAfterDuration', function () {
  552. const userId1 = new ObjectId()
  553. const userId2 = new ObjectId()
  554. beforeEach(function (ctx) {
  555. ctx.deletedUsers = [
  556. {
  557. user: { _id: userId1 },
  558. deleterData: { deletedUserId: userId1 },
  559. save: sinon.stub().resolves(),
  560. },
  561. {
  562. user: { _id: userId2 },
  563. deleterData: { deletedUserId: userId2 },
  564. save: sinon.stub().resolves(),
  565. },
  566. ]
  567. ctx.DeletedUserMock.expects('find')
  568. .withArgs({
  569. 'deleterData.deletedAt': {
  570. $lt: new Date(moment().subtract(90, 'days')),
  571. },
  572. user: {
  573. $type: 'object',
  574. },
  575. })
  576. .chain('exec')
  577. .resolves(ctx.deletedUsers)
  578. for (const deletedUser of ctx.deletedUsers) {
  579. ctx.DeletedUserMock.expects('findOne')
  580. .withArgs({
  581. 'deleterData.deletedUserId': deletedUser.deleterData.deletedUserId,
  582. })
  583. .chain('exec')
  584. .resolves(deletedUser)
  585. }
  586. })
  587. it('clears data from all deleted users', async function (ctx) {
  588. await ctx.UserDeleter.promises.expireDeletedUsersAfterDuration()
  589. for (const deletedUser of ctx.deletedUsers) {
  590. expect(deletedUser.user).to.be.undefined
  591. expect(deletedUser.save.called).to.be.true
  592. }
  593. })
  594. it('deletes audit logs for all deleted users', async function (ctx) {
  595. await ctx.UserDeleter.promises.expireDeletedUsersAfterDuration()
  596. for (const deletedUser of ctx.deletedUsers) {
  597. expect(ctx.UserAuditLogEntry.deleteMany).to.have.been.calledWith({
  598. userId: deletedUser.deleterData.deletedUserId,
  599. })
  600. }
  601. })
  602. })
  603. describe('expireDeletedUser', function () {
  604. beforeEach(function (ctx) {
  605. ctx.mockedDeletedUser = sinon.mock(
  606. new DeletedUser({
  607. user: ctx.user,
  608. deleterData: {
  609. deleterIpAddress: '1.1.1.1',
  610. deletedUserId: ctx.userId,
  611. },
  612. })
  613. )
  614. ctx.deletedUser = ctx.mockedDeletedUser.object
  615. ctx.mockedDeletedUser.expects('save').resolves()
  616. ctx.DeletedUserMock.expects('findOne')
  617. .withArgs({ 'deleterData.deletedUserId': ctx.userId })
  618. .chain('exec')
  619. .resolves(ctx.deletedUser)
  620. })
  621. afterEach(function (ctx) {
  622. ctx.mockedDeletedUser.restore()
  623. })
  624. it('should find the user by user ID', async function (ctx) {
  625. await ctx.UserDeleter.promises.expireDeletedUser(ctx.userId)
  626. ctx.DeletedUserMock.verify()
  627. })
  628. it('should remove the user data from mongo', async function (ctx) {
  629. await ctx.UserDeleter.promises.expireDeletedUser(ctx.userId)
  630. expect(ctx.deletedUser.user).not.to.exist
  631. })
  632. it('should remove the IP address from mongo', async function (ctx) {
  633. await ctx.UserDeleter.promises.expireDeletedUser(ctx.userId)
  634. expect(ctx.deletedUser.deleterData.ipAddress).not.to.exist
  635. })
  636. it('should not delete other deleterData fields', async function (ctx) {
  637. await ctx.UserDeleter.promises.expireDeletedUser(ctx.userId)
  638. expect(ctx.deletedUser.deleterData.deletedUserId).to.equal(ctx.userId)
  639. })
  640. it('should save the record to mongo', async function (ctx) {
  641. await ctx.UserDeleter.promises.expireDeletedUser(ctx.userId)
  642. ctx.mockedDeletedUser.verify()
  643. })
  644. it('should fire the expireDeletedUser hook for modules', async function (ctx) {
  645. await ctx.UserDeleter.promises.expireDeletedUser(ctx.userId)
  646. expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
  647. 'expireDeletedUser',
  648. ctx.userId
  649. )
  650. })
  651. describe('when called as a callback', function () {
  652. it('should expire the user', async function (ctx) {
  653. await new Promise(resolve => {
  654. ctx.UserDeleter.expireDeletedUser(ctx.userId, err => {
  655. expect(err).not.to.exist
  656. ctx.DeletedUserMock.verify()
  657. ctx.mockedDeletedUser.verify()
  658. expect(ctx.deletedUser.user).not.to.exist
  659. expect(ctx.deletedUser.deleterData.ipAddress).not.to.exist
  660. resolve()
  661. })
  662. })
  663. })
  664. })
  665. })
  666. })