dashboardSupervisor.test.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { describe, expect, it } from 'vitest'
  2. import {
  3. DashboardSupervisor,
  4. DEFAULT_DASHBOARD_USERNAME,
  5. } from '../../src/server/dashboardSupervisor.js'
  6. function supervisor(overrides: {
  7. values?: Record<string, string>
  8. running?: boolean
  9. runningHost?: '127.0.0.1' | '0.0.0.0'
  10. allowLan?: boolean
  11. resolveLanProbeHost?: () => string | undefined
  12. } = {}) {
  13. const values = { ...overrides.values }
  14. let running = overrides.running ?? false
  15. let runningHost = overrides.runningHost ?? '127.0.0.1'
  16. const lanProbeHost = '192.168.1.20'
  17. const calls: string[][] = []
  18. const launches: string[][] = []
  19. const instance = new DashboardSupervisor({
  20. allowLan: overrides.allowLan ?? false,
  21. lanProbeHost: overrides.resolveLanProbeHost ? undefined : lanProbeHost,
  22. resolveLanProbeHost: overrides.resolveLanProbeHost,
  23. run(args) {
  24. calls.push([...args])
  25. if (args[0] === 'config' && args[1] === 'get') return values[args[2]] ?? ''
  26. if (args[0] === 'config' && args[1] === 'set') {
  27. values[args[2]] = args[3]
  28. return ''
  29. }
  30. if (args[0] === 'dashboard' && args[1] === '--stop') {
  31. running = false
  32. return ''
  33. }
  34. return ''
  35. },
  36. launch(args) {
  37. launches.push([...args])
  38. running = true
  39. runningHost = args[2] as typeof runningHost
  40. },
  41. probe: async (host) => running && (
  42. host === '127.0.0.1'
  43. || (host === lanProbeHost && runningHost === '0.0.0.0')
  44. ),
  45. log: () => undefined,
  46. })
  47. return { instance, calls, launches, values }
  48. }
  49. describe('DashboardSupervisor', () => {
  50. it('configures the requested default credentials and starts an unavailable dashboard', async () => {
  51. const value = supervisor()
  52. await value.instance.checkNow()
  53. expect(value.values['dashboard.basic_auth.username']).toBe(DEFAULT_DASHBOARD_USERNAME)
  54. expect(value.values['dashboard.basic_auth.password_hash']).toMatch(/^scrypt\$16384\$8\$1\$/)
  55. expect(value.values['dashboard.basic_auth.secret']).toMatch(/.{32,}/)
  56. expect(value.launches).toEqual([['dashboard', '--host', '127.0.0.1', '--no-open']])
  57. })
  58. it('does not overwrite configured dashboard credentials', async () => {
  59. const value = supervisor({
  60. running: true,
  61. values: {
  62. 'dashboard.basic_auth.username': 'operator',
  63. 'dashboard.basic_auth.password_hash': 'scrypt$existing',
  64. 'dashboard.basic_auth.secret': 'configured-secret',
  65. },
  66. })
  67. await value.instance.checkNow()
  68. expect(value.calls.filter((args) => args[1] === 'set')).toEqual([])
  69. expect(value.launches).toEqual([])
  70. })
  71. it('restarts a running dashboard after filling missing authentication and uses LAN binding when enabled', async () => {
  72. const value = supervisor({ running: true, allowLan: true })
  73. await value.instance.checkNow()
  74. expect(value.calls).toContainEqual(['dashboard', '--stop'])
  75. expect(value.launches).toEqual([['dashboard', '--host', '0.0.0.0', '--no-open']])
  76. })
  77. it('moves an authenticated loopback dashboard onto the LAN when managed installation requires it', async () => {
  78. const value = supervisor({
  79. running: true,
  80. runningHost: '127.0.0.1',
  81. allowLan: true,
  82. values: {
  83. 'dashboard.basic_auth.username': 'operator',
  84. 'dashboard.basic_auth.password_hash': 'scrypt$existing',
  85. 'dashboard.basic_auth.secret': 'configured-secret',
  86. },
  87. })
  88. await value.instance.checkNow()
  89. expect(value.calls).toContainEqual(['dashboard', '--stop'])
  90. expect(value.launches).toEqual([['dashboard', '--host', '0.0.0.0', '--no-open']])
  91. })
  92. it('keeps an authenticated dashboard running when it already listens on the LAN', async () => {
  93. const value = supervisor({
  94. running: true,
  95. runningHost: '0.0.0.0',
  96. allowLan: true,
  97. values: {
  98. 'dashboard.basic_auth.username': 'operator',
  99. 'dashboard.basic_auth.password_hash': 'scrypt$existing',
  100. 'dashboard.basic_auth.secret': 'configured-secret',
  101. },
  102. })
  103. await value.instance.checkNow()
  104. expect(value.calls).not.toContainEqual(['dashboard', '--stop'])
  105. expect(value.launches).toEqual([])
  106. })
  107. it('moves an authenticated LAN dashboard back to loopback when unmanaged LAN is requested', async () => {
  108. const value = supervisor({
  109. running: true,
  110. runningHost: '0.0.0.0',
  111. allowLan: false,
  112. values: {
  113. 'dashboard.basic_auth.username': 'operator',
  114. 'dashboard.basic_auth.password_hash': 'scrypt$existing',
  115. 'dashboard.basic_auth.secret': 'configured-secret',
  116. },
  117. })
  118. await value.instance.checkNow()
  119. expect(value.calls).toContainEqual(['dashboard', '--stop'])
  120. expect(value.launches).toEqual([['dashboard', '--host', '127.0.0.1', '--no-open']])
  121. })
  122. it('rechecks LAN binding after the network interface becomes available', async () => {
  123. let networkReady = false
  124. const value = supervisor({
  125. running: true,
  126. runningHost: '127.0.0.1',
  127. allowLan: true,
  128. resolveLanProbeHost: () => networkReady ? '192.168.1.20' : undefined,
  129. values: {
  130. 'dashboard.basic_auth.username': 'operator',
  131. 'dashboard.basic_auth.password_hash': 'scrypt$existing',
  132. 'dashboard.basic_auth.secret': 'configured-secret',
  133. },
  134. })
  135. await value.instance.checkNow()
  136. expect(value.launches).toEqual([])
  137. networkReady = true
  138. await value.instance.checkNow()
  139. expect(value.calls).toContainEqual(['dashboard', '--stop'])
  140. expect(value.launches).toEqual([['dashboard', '--host', '0.0.0.0', '--no-open']])
  141. })
  142. it('explicitly restarts a healthy dashboard and waits for 9119 to return', async () => {
  143. const value = supervisor({
  144. running: true,
  145. values: {
  146. 'dashboard.basic_auth.username': 'operator',
  147. 'dashboard.basic_auth.password_hash': 'scrypt$existing',
  148. 'dashboard.basic_auth.secret': 'configured-secret',
  149. },
  150. })
  151. await value.instance.restart()
  152. expect(value.calls).toContainEqual(['dashboard', '--stop'])
  153. expect(value.launches).toEqual([['dashboard', '--host', '127.0.0.1', '--no-open']])
  154. })
  155. })