ReAddDeletedEmailsTests.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import { promisify } from 'node:util'
  2. import { exec } from 'node:child_process'
  3. import { expect } from 'chai'
  4. import { filterOutput } from './helpers/settings.mjs'
  5. import { db, ObjectId } from '../../../app/src/infrastructure/mongodb.js'
  6. import fs from 'node:fs/promises'
  7. const CSV_FILENAME = '/tmp/re_add_deleted_emails.csv'
  8. async function runScript(commit) {
  9. const result = await promisify(exec)(
  10. ['node', 'scripts/re_add_deleted_emails.mjs', commit && '--commit']
  11. .filter(Boolean)
  12. .join(' ')
  13. )
  14. return {
  15. ...result,
  16. stdout: result.stdout.split('\n').filter(filterOutput),
  17. }
  18. }
  19. /**
  20. * @param {[string, string[]][]} userEmails
  21. */
  22. const createUsers = async userEmails =>
  23. Promise.all(
  24. userEmails.map(async ([email, emails]) => {
  25. const _id = new ObjectId()
  26. await db.users.insertOne({
  27. _id,
  28. email,
  29. emails: emails.map(email => ({ email })),
  30. features: {},
  31. })
  32. return _id
  33. })
  34. )
  35. async function generateCsv(users) {
  36. const text = 'User ID,Email'
  37. const userRows = users.map(user => {
  38. return `${user._id.toString()},${user.email}`
  39. })
  40. await fs.writeFile(CSV_FILENAME, [text, ...userRows].join('\n'))
  41. }
  42. describe('scripts/re_add_deleted_emails', function () {
  43. let userIds
  44. afterEach(async function () {
  45. try {
  46. await fs.unlink(CSV_FILENAME)
  47. } catch (err) {
  48. // Ignore errors if file doesn't exist
  49. }
  50. })
  51. describe('when user IDs dont match', function () {
  52. beforeEach(async function () {
  53. userIds = await createUsers([['mismatch1@xmpl.com', []]])
  54. await generateCsv([{ _id: new ObjectId(), email: 'mismatch2@xmpl.com' }])
  55. })
  56. it('doesnt add new emails', async function () {
  57. const { stdout } = await runScript(true)
  58. expect(stdout).to.include('Total emails in the CSV: 1')
  59. expect(stdout).to.include('Total users in the CSV: 1')
  60. expect(stdout).to.include('Users not found: 1')
  61. expect(stdout).to.include('Added emails: 0')
  62. const updatedUser = await db.users.findOne({ _id: userIds[0] })
  63. expect(updatedUser.emails).to.have.length(0)
  64. })
  65. })
  66. describe('when email address is invalid', function () {
  67. beforeEach(async function () {
  68. userIds = await createUsers([['user@xmpl.com', []]])
  69. await generateCsv([{ _id: userIds[0], email: 'inv@lid@xmpl.com' }])
  70. })
  71. it('throws', async function () {
  72. await expect(runScript(true)).to.eventually.be.rejectedWith(
  73. 'invalid email'
  74. )
  75. const updatedUser = await db.users.findOne({ _id: userIds[0] })
  76. expect(updatedUser.emails).to.have.length(0)
  77. })
  78. it('throws even without --commit', async function () {
  79. await expect(runScript(false)).to.eventually.be.rejectedWith(
  80. 'invalid email'
  81. )
  82. const updatedUser = await db.users.findOne({ _id: userIds[0] })
  83. expect(updatedUser.emails).to.have.length(0)
  84. })
  85. })
  86. describe('when new email is used by another user', function () {
  87. beforeEach(async function () {
  88. userIds = await createUsers([
  89. ['user1@xmpl.com', []],
  90. ['user2@xmpl.com', ['new-email@xmpl.com']],
  91. ])
  92. await generateCsv([{ _id: userIds[0], email: 'new-email@xmpl.com' }])
  93. })
  94. it('doesnt add new emails', async function () {
  95. const { stdout } = await runScript(true)
  96. expect(stdout).to.include('Total emails in the CSV: 1')
  97. expect(stdout).to.include('Total users in the CSV: 1')
  98. expect(stdout).to.include('Users not found: 0')
  99. expect(stdout).to.include('Already in use: ["new-email@xmpl.com"]')
  100. expect(stdout).to.include('Added emails: 0')
  101. const updatedUser = await db.users.findOne({ _id: userIds[0] })
  102. expect(updatedUser.emails).to.have.length(0)
  103. })
  104. })
  105. describe('when the user has 0 email in the array', function () {
  106. beforeEach(async function () {
  107. userIds = await createUsers([['user@xmpl.com', []]])
  108. })
  109. it('adds the primary email to the user', async function () {
  110. await generateCsv([{ _id: userIds[0], email: 'user@xmpl.com' }])
  111. const { stdout } = await runScript(true)
  112. expect(stdout).to.include('Total emails in the CSV: 1')
  113. expect(stdout).to.include('Total users in the CSV: 1')
  114. expect(stdout).to.include('Users not found: 0')
  115. expect(stdout).to.include('Added emails: 1')
  116. const updatedUser = await db.users.findOne({ _id: userIds[0] })
  117. expect(updatedUser.emails).to.have.length(1)
  118. expect(updatedUser.emails[0].email).to.equal('user@xmpl.com')
  119. expect(updatedUser.emails[0].reversedHostname).to.equal('moc.lpmx')
  120. expect(updatedUser.emails[0].confirmedAt).to.be.an.instanceof(Date)
  121. expect(updatedUser.emails[0].createdAt).to.be.an.instanceof(Date)
  122. expect(updatedUser.emails[0].reconfirmedAt).to.be.an.instanceof(Date)
  123. const auditLogs = await db.userAuditLogEntries
  124. .find({ userId: userIds[0] })
  125. .toArray()
  126. expect(auditLogs).to.have.length(1)
  127. expect(auditLogs[0].operation).to.equal('add-email')
  128. expect(auditLogs[0].info).to.deep.include({
  129. script: true,
  130. note: 'fix wrongly removed unconfirmed secondary email',
  131. newSecondaryEmail: 'user@xmpl.com',
  132. })
  133. })
  134. it('adds the secondary email to the user', async function () {
  135. await generateCsv([{ _id: userIds[0], email: 'new-email@xmpl.com' }])
  136. const { stdout } = await runScript(true)
  137. expect(stdout).to.include('Total emails in the CSV: 1')
  138. expect(stdout).to.include('Total users in the CSV: 1')
  139. expect(stdout).to.include('Users not found: 0')
  140. expect(stdout).to.include('Added emails: 1')
  141. const updatedUser = await db.users.findOne({ _id: userIds[0] })
  142. expect(updatedUser.emails).to.have.length(1)
  143. expect(updatedUser.emails[0].email).to.equal('new-email@xmpl.com')
  144. expect(updatedUser.emails[0].reversedHostname).to.equal('moc.lpmx')
  145. expect(updatedUser.emails[0].confirmedAt).to.be.an.instanceof(Date)
  146. expect(updatedUser.emails[0].createdAt).to.be.an.instanceof(Date)
  147. expect(updatedUser.emails[0].reconfirmedAt).to.be.an.instanceof(Date)
  148. const auditLogs = await db.userAuditLogEntries
  149. .find({ userId: userIds[0] })
  150. .toArray()
  151. expect(auditLogs).to.have.length(1)
  152. expect(auditLogs[0].operation).to.equal('add-email')
  153. expect(auditLogs[0].info).to.deep.include({
  154. script: true,
  155. note: 'fix wrongly removed unconfirmed secondary email',
  156. newSecondaryEmail: 'new-email@xmpl.com',
  157. })
  158. })
  159. it('doesnt add new emails without --commit', async function () {
  160. await generateCsv([{ _id: userIds[0], email: 'new-email@xmpl.com' }])
  161. const { stdout } = await runScript(false)
  162. expect(stdout).to.include('Dry-run, use --commit to apply changes')
  163. const updatedUser = await db.users.findOne({ _id: userIds[0] })
  164. expect(updatedUser.emails).to.have.length(0)
  165. })
  166. })
  167. describe('when the user has several emails in the array', function () {
  168. beforeEach(async function () {
  169. userIds = await createUsers([
  170. [
  171. 'user@xmpl.com',
  172. ['email1@xmpl.com', 'email2@xmpl.com', 'email3@xmpl.com'],
  173. ],
  174. ])
  175. await generateCsv([{ _id: userIds[0], email: 'new-email@xmpl.com' }])
  176. })
  177. it('adds the email to the user', async function () {
  178. const { stdout } = await runScript(true)
  179. expect(stdout).to.include('Total emails in the CSV: 1')
  180. expect(stdout).to.include('Total users in the CSV: 1')
  181. expect(stdout).to.include('Users not found: 0')
  182. expect(stdout).to.include('Added emails: 1')
  183. const updatedUser = await db.users.findOne({ _id: userIds[0] })
  184. expect(updatedUser.emails).to.have.length(4)
  185. expect(updatedUser.emails[3].email).to.equal('new-email@xmpl.com')
  186. expect(updatedUser.emails[3].reversedHostname).to.equal('moc.lpmx')
  187. expect(updatedUser.emails[3].confirmedAt).to.be.an.instanceof(Date)
  188. expect(updatedUser.emails[3].createdAt).to.be.an.instanceof(Date)
  189. expect(updatedUser.emails[3].reconfirmedAt).to.be.an.instanceof(Date)
  190. })
  191. it('doesnt add new emails without --commit', async function () {
  192. const { stdout } = await runScript(false)
  193. expect(stdout).to.include('Dry-run, use --commit to apply changes')
  194. const updatedUser = await db.users.findOne({ _id: userIds[0] })
  195. expect(updatedUser.emails).to.have.length(3)
  196. })
  197. })
  198. describe('all of the above', function () {
  199. beforeEach(async function () {
  200. userIds = await createUsers([
  201. ['user0@xmpl.com', []],
  202. ['user1@xmpl.com', []],
  203. ['user2@xmpl.com', ['a@xmpl.com', 'b@xmpl.com', 'c@xmpl.com']],
  204. ['user3@xmpl.com', ['d@xmpl.com', 'e@xmpl.com', 'f@xmpl.com']],
  205. ['user4@xmpl.com', ['x@xmpl.com', 'y@xmpl.com', 'z@xmpl.com']],
  206. ['user5@xmpl.com', ['u@xmpl.com', 'v@xmpl.com', 'w@xmpl.com']],
  207. ])
  208. await generateCsv([
  209. { _id: userIds[1], email: 'new1@xmpl.com' },
  210. { _id: userIds[1], email: 'user1@xmpl.com' },
  211. { _id: userIds[1], email: 'new2@xmpl.com' },
  212. { _id: userIds[2], email: 'new3@xmpl.com' },
  213. { _id: userIds[2], email: 'new4@xmpl.com' },
  214. { _id: userIds[2], email: 'user2@xmpl.com' },
  215. { _id: userIds[2], email: 'user3@xmpl.com' },
  216. { _id: userIds[3], email: 'd@xmpl.com' },
  217. { _id: userIds[5], email: 'a@xmpl.com' },
  218. { _id: new ObjectId(), email: 'a@xmpl.com' },
  219. ])
  220. })
  221. it('updates users', async function () {
  222. const { stdout } = await runScript(true)
  223. expect(stdout).to.include('Total emails in the CSV: 10')
  224. expect(stdout).to.include('Total users in the CSV: 5')
  225. expect(stdout).to.include('Users not found: 1')
  226. expect(stdout).to.include(
  227. 'Already in use: ["user3@xmpl.com","a@xmpl.com"]'
  228. )
  229. expect(stdout).to.include('Already OK: ["d@xmpl.com"]')
  230. expect(stdout).to.include('Primary: ["user1@xmpl.com","user2@xmpl.com"]')
  231. expect(stdout).to.include(
  232. 'Secondary: ["new1@xmpl.com","new2@xmpl.com","new3@xmpl.com","new4@xmpl.com"]'
  233. )
  234. expect(stdout).to.include(
  235. 'Added emails: ["new1@xmpl.com","user1@xmpl.com","new2@xmpl.com","new3@xmpl.com","new4@xmpl.com","user2@xmpl.com"]'
  236. )
  237. const user0 = await db.users.findOne({ _id: userIds[0] })
  238. const user1 = await db.users.findOne({ _id: userIds[1] })
  239. const user2 = await db.users.findOne({ _id: userIds[2] })
  240. const user3 = await db.users.findOne({ _id: userIds[3] })
  241. const user4 = await db.users.findOne({ _id: userIds[4] })
  242. const user5 = await db.users.findOne({ _id: userIds[5] })
  243. expect(user0.emails).to.have.length(0)
  244. expect(user1.emails).to.have.length(3) // new1, user1, new2
  245. expect(user2.emails).to.have.length(6) // a, b, c, new3, new4, user2
  246. expect(user3.emails).to.have.length(3) // d, e, f
  247. expect(user4.emails).to.have.length(3) // x, y, z
  248. expect(user5.emails).to.have.length(3) // u, v, w
  249. })
  250. })
  251. })