ClearSessionsSetMustReconfirmTests.mjs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import { exec } from 'node:child_process'
  2. import { promisify } from 'node:util'
  3. import { expect } from 'chai'
  4. import logger from '@overleaf/logger'
  5. import { ObjectId, db } from '../../../app/src/infrastructure/mongodb.js'
  6. import fs from 'node:fs/promises'
  7. import UserHelper from './helpers/User.mjs'
  8. import UserGetter from '../../../app/src/Features/User/UserGetter.js'
  9. const User = UserHelper.promises
  10. const TEST_FILE_PATH = '/tmp/test-users.txt'
  11. describe('ClearSessionsSetMustReconfirm', function () {
  12. let user1, user2, user3, user4, usersMustReconfirm, usersMustNotReconfirm
  13. beforeEach('create test users', async function () {
  14. user1 = new User()
  15. user2 = new User() // not in the file
  16. user3 = new User() // not in the file
  17. user4 = new User()
  18. await user1.login()
  19. await user2.login()
  20. await user3.login()
  21. await user4.login()
  22. usersMustReconfirm = [user1, user4]
  23. usersMustNotReconfirm = [user2, user3]
  24. })
  25. beforeEach('create test file', async function () {
  26. await fs.writeFile(
  27. TEST_FILE_PATH,
  28. usersMustReconfirm.map(user => user._id.toString()).join('\n')
  29. )
  30. })
  31. afterEach('cleanup test file', async function () {
  32. try {
  33. await fs.unlink(TEST_FILE_PATH)
  34. } catch (err) {
  35. // Ignore error if file doesn't exist
  36. }
  37. })
  38. async function runScript(filePath = TEST_FILE_PATH) {
  39. let result
  40. try {
  41. result = await promisify(exec)(
  42. ['VERBOSE_LOGGING=true']
  43. .concat(['node', 'scripts/clear_sessions_set_must_reconfirm.mjs'])
  44. .concat([filePath])
  45. .join(' ')
  46. )
  47. } catch (error) {
  48. logger.error({ error }, 'script failed')
  49. throw error
  50. }
  51. const { stdout: stdOut } = result
  52. expect(stdOut).to.include('DONE.')
  53. return result
  54. }
  55. describe('processing users', function () {
  56. it('should process all users successfully', async function () {
  57. const { stdout } = await runScript()
  58. expect(stdout).to.include(`${usersMustReconfirm.length} successful`)
  59. expect(stdout).to.include('0 failed to clear sessions')
  60. expect(stdout).to.include('0 failed to set must_reconfirm')
  61. for (const user of usersMustReconfirm) {
  62. const updatedUser = await UserGetter.promises.getUser({
  63. _id: user._id,
  64. })
  65. expect(updatedUser.must_reconfirm).to.be.true
  66. }
  67. for (const user of usersMustNotReconfirm) {
  68. const updatedUser = await UserGetter.promises.getUser({
  69. _id: user._id,
  70. })
  71. expect(updatedUser.must_reconfirm).to.be.false
  72. }
  73. })
  74. it('should handle invalid user IDs in file', async function () {
  75. await fs.writeFile(
  76. TEST_FILE_PATH,
  77. [
  78. 'invalid-id',
  79. ...usersMustReconfirm.map(user => user._id.toString()).join('\n'),
  80. ].join('\n')
  81. )
  82. try {
  83. await runScript()
  84. expect.fail('Should have thrown error')
  85. } catch (error) {
  86. expect(error.message).to.include('user ID not valid')
  87. }
  88. })
  89. it('should process large number of users with concurrency limit', async function () {
  90. const manyUserIds = Array.from({ length: 15 }, () =>
  91. new ObjectId().toString()
  92. )
  93. await fs.writeFile(TEST_FILE_PATH, manyUserIds.join('\n'))
  94. const { stdout } = await runScript()
  95. expect(stdout).to.include('15 successful')
  96. })
  97. })
  98. describe('error handling', function () {
  99. beforeEach('ensure test file exists', async function () {
  100. await fs.writeFile(
  101. TEST_FILE_PATH,
  102. usersMustReconfirm.map(user => user._id.toString()).join('\n')
  103. )
  104. })
  105. it('should report failed user updates', async function () {
  106. await db.users.updateOne(
  107. { _id: user1._id },
  108. { $set: { must_reconfirm: null } }
  109. )
  110. const { stdout } = await runScript()
  111. expect(stdout).to.include('failed to set must_reconfirm')
  112. })
  113. it('should handle missing input file', async function () {
  114. try {
  115. await runScript(['/non/existent/file'])
  116. expect.fail('Should have thrown error')
  117. } catch (error) {
  118. expect(error.message).to.include('ENOENT')
  119. }
  120. })
  121. })
  122. describe('audit logging', function () {
  123. it('should create audit log entries for processed users', async function () {
  124. await runScript()
  125. for (const user of usersMustReconfirm) {
  126. const auditLogEntry = await user.getAuditLog()
  127. expect(auditLogEntry).to.exist
  128. expect(auditLogEntry[0].operation).to.equal('login')
  129. expect(auditLogEntry[1].operation).to.equal('must-reset-password-set')
  130. expect(auditLogEntry[1].initiatorId).to.be.undefined
  131. expect(auditLogEntry[1].ipAddress).to.be.undefined
  132. expect(auditLogEntry[1].info).to.deep.equal({ script: true })
  133. }
  134. for (const user of usersMustNotReconfirm) {
  135. const auditLogEntry = await user.getAuditLog()
  136. expect(auditLogEntry).to.exist
  137. expect(auditLogEntry[0].operation).to.equal('login')
  138. expect(auditLogEntry[1]).to.be.undefined
  139. }
  140. })
  141. })
  142. })