onboarding.test.mjs 5.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import { DesktopOnboarding } from './onboarding.mjs'
  4. function fixture(overrides = {}) {
  5. const calls = []
  6. const flow = new DesktopOnboarding({
  7. remoteServer: () => 'http://remote.test:15300',
  8. prepareLocal: async () => { calls.push('prepare'); return 'http://127.0.0.1:15300' },
  9. inspect: async () => ({ authenticated: false, setupRequired: true }),
  10. authenticate: async value => { calls.push(value); return { username: value.username } },
  11. activate: async value => { calls.push({ mode: value.mode, remember: value.remember }) },
  12. navigate: async url => { calls.push(url) }, ...overrides,
  13. })
  14. return { flow, calls }
  15. }
  16. test('selection, local preparation and account creation run in order; snapshots contain no credentials', async () => {
  17. const { flow, calls } = fixture()
  18. await assert.rejects(() => flow.submit({ username: 'admin', password: 'password' }), /检测/)
  19. flow.select('local')
  20. await flow.prepare()
  21. assert.equal(flow.snapshot().setupRequired, true)
  22. await assert.rejects(() => flow.submit({ username: 'admin', password: 'password', confirmation: 'different' }), /不一致/)
  23. await flow.submit({ username: 'admin', password: 'password', confirmation: 'password', remember: true })
  24. assert.deepEqual(calls, ['prepare', { mode: 'local', serverURL: 'http://127.0.0.1:15300', setup: true, username: 'admin', password: 'password' }, { mode: 'local', remember: true }, 'http://127.0.0.1:15300'])
  25. assert.equal(flow.snapshot().active, false)
  26. assert.equal(JSON.stringify(flow.snapshot()).includes('password'), false)
  27. assert.doesNotThrow(() => flow.open({ mode: 'remote' }), 'completed flows can reopen in the same window')
  28. })
  29. test('an in-flight server check cannot be switched underneath credentials', async () => {
  30. let release
  31. const { flow, calls } = fixture({ inspect: () => new Promise(resolve => { release = resolve }) })
  32. flow.select('remote')
  33. const checking = flow.prepare()
  34. assert.throws(() => flow.select('local'), /尚未完成/)
  35. await assert.rejects(() => flow.submit({ username: 'admin', password: 'password' }), /检测/)
  36. release({ authenticated: false }); await checking
  37. assert.equal(flow.snapshot().serverURL, 'http://remote.test:15300')
  38. assert.deepEqual(calls, [])
  39. })
  40. test('remote setup is blocked; failed checks and wrong passwords stay inline and can be retried', async () => {
  41. let checks = 0, logins = 0
  42. const { flow } = fixture({
  43. inspect: async () => ++checks === 1 ? { setupRequired: true, authenticated: false } : { authenticated: false },
  44. authenticate: async () => { if (++logins === 1) throw new Error('密码错误'); return { username: 'user' } },
  45. })
  46. flow.select('remote'); await flow.prepare()
  47. assert.match(flow.snapshot().error, /本机创建管理员/)
  48. assert.equal(flow.snapshot().phase, 'error')
  49. await flow.prepare()
  50. await flow.submit({ username: 'user', password: 'bad' })
  51. assert.equal(flow.snapshot().phase, 'ready'); assert.equal(flow.snapshot().error, '密码错误')
  52. await flow.submit({ username: 'user', password: 'good' })
  53. assert.equal(flow.snapshot().active, false)
  54. })
  55. test('valid remembered sessions skip credentials, but reauthorization requests a fresh login', async () => {
  56. const { flow, calls } = fixture({ inspect: async () => ({ authenticated: true, user: { username: 'user' } }) })
  57. flow.open({ mode: 'remote', serverURL: 'http://remote.test', remember: true })
  58. await flow.prepare({ autoEnter: true })
  59. assert.equal(flow.snapshot().active, false)
  60. flow.open({ mode: 'remote', serverURL: 'http://remote.test', forceLogin: true })
  61. await flow.prepare({ autoEnter: true })
  62. assert.equal(flow.snapshot().active, true); assert.equal(flow.snapshot().authenticated, false)
  63. assert.equal(calls.length, 2)
  64. })
  65. test('failed computer enrollment preserves login and offers inline continuation', async () => {
  66. const { flow, calls } = fixture({ inspect: async () => ({ authenticated: false }), authenticate: async () => ({ username: 'admin', warning: '电脑授权未恢复' }) })
  67. flow.select('remote'); await flow.prepare(); await flow.submit({ username: 'admin', password: 'password' })
  68. assert.equal(flow.snapshot().phase, 'ready'); assert.equal(flow.snapshot().authenticated, true)
  69. assert.match(flow.snapshot().error, /授权未恢复/); assert.deepEqual(calls, [])
  70. await flow.submit(); assert.equal(flow.snapshot().active, false)
  71. })
  72. test('registration stays anonymous and does not enter or enroll a computer', async () => {
  73. const calls = []
  74. const flow = new DesktopOnboarding({ remoteServer: () => 'http://127.0.0.1:15300', inspect: async () => ({ registrationAvailable: true }), register: async input => { calls.push(input) }, authenticate: async () => { throw new Error('must not log in') }, activate: async () => { throw new Error('must not activate') } })
  75. flow.select('remote'); await flow.prepare()
  76. const next = await flow.submit({ register: true, username: 'child', password: 'password', confirmation: 'password' })
  77. assert.equal(next.authenticated, false)
  78. assert.match(next.registrationNotice, /等待管理员开通/)
  79. assert.equal(calls.length, 1)
  80. assert.equal(JSON.stringify(next).includes('password'), false)
  81. await flow.prepare({ serverURL: 'http://another-server.test' })
  82. assert.equal(flow.snapshot().registrationNotice, '', 'registration belongs to the checked server')
  83. })