backgroundActivation.test.ts 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. // @vitest-environment node
  2. import { mkdtempSync, rmSync } from 'node:fs'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { afterEach, expect, it, vi } from 'vitest'
  6. import { createNodeServer } from '../../src/server/app'
  7. import { loadServerConfig } from '../../src/server/config'
  8. import { PushCoordinator } from '../../src/server/pushCoordinator'
  9. import { WorkspaceMemorySynthesis } from '../../src/server/workspaceMemorySynthesis'
  10. import { OpenVikingSessionSync } from '../../src/server/openVikingSessionSync'
  11. import { createAuthenticatedApplication } from './authenticatedApplication'
  12. afterEach(() => vi.restoreAllMocks())
  13. function fixture(deferBackground: boolean) {
  14. const home = mkdtempSync(join(tmpdir(), 'yaoyao-background-activation-'))
  15. const memory = vi.spyOn(WorkspaceMemorySynthesis.prototype, 'start').mockImplementation(() => {})
  16. const memorySync = vi.spyOn(OpenVikingSessionSync.prototype, 'start').mockImplementation(() => {})
  17. const push = new PushCoordinator({ home, autoFlush: false,
  18. fcmProviderFactory: () => ({ send: async () => ({ disposition: 'success', status: 200 }) }) })
  19. const runtime = createAuthenticatedApplication({ deferBackground, push,
  20. config: loadServerConfig({ HERMES_YAOYAO_HOME: home, HERMES_YAOYAO_ALLOWED_HOSTS: '127.0.0.1' }) })
  21. const starts = {
  22. memory, memorySync,
  23. workspace: vi.spyOn(runtime.workspaceRuntime, 'start').mockImplementation(() => {}),
  24. routines: vi.spyOn(runtime.workspaceRoutines, 'start').mockImplementation(() => {}),
  25. grok: vi.spyOn(runtime.grokAuth, 'start').mockImplementation(() => {}),
  26. }
  27. const localVm = vi.spyOn(runtime.localVm, 'start').mockResolvedValue(undefined)
  28. const chatStart = vi.spyOn(runtime.chatPushJobs, 'start').mockImplementation(() => {})
  29. const chatStop = vi.spyOn(runtime.chatPushJobs, 'stop').mockImplementation(() => {})
  30. const node = createNodeServer(runtime, { deferBackground })
  31. const listen = () => new Promise<void>(resolve => node.server.listen(0, '127.0.0.1', resolve))
  32. const close = async () => { try { await node.close() } finally { rmSync(home, { recursive: true, force: true }) } }
  33. return { runtime, push, node, starts, localVm, chatStart, chatStop, listen, close }
  34. }
  35. it('keeps environment detection idle after listening and starts all background work once on activation', async () => {
  36. const f = fixture(true)
  37. try {
  38. await f.listen()
  39. for (const start of Object.values(f.starts)) expect(start).not.toHaveBeenCalled()
  40. expect(f.localVm).not.toHaveBeenCalled()
  41. expect(f.chatStart).not.toHaveBeenCalled()
  42. expect(f.chatStop).not.toHaveBeenCalled()
  43. await f.push.configureFCM({ serviceAccountFile: '/private/unused.json', projectId: 'fixture', packageName: 'cn.samien.yaoyao.hermes' })
  44. expect(f.chatStart).not.toHaveBeenCalled()
  45. f.node.startBackground()
  46. f.node.startBackground()
  47. for (const start of Object.values(f.starts)) expect(start).toHaveBeenCalledTimes(1)
  48. expect(f.localVm).toHaveBeenCalledTimes(1)
  49. const address = f.node.server.address()
  50. expect(f.localVm).toHaveBeenCalledWith(`http://127.0.0.1:${typeof address === 'object' && address?.port}`)
  51. expect(f.chatStart).toHaveBeenCalledTimes(1)
  52. await f.push.configureFCM()
  53. expect(f.chatStop).toHaveBeenCalledTimes(1)
  54. } finally { await f.close() }
  55. f.node.startBackground()
  56. f.runtime.startBackground()
  57. for (const start of Object.values(f.starts)) expect(start).toHaveBeenCalledTimes(1)
  58. })
  59. it('holds local VM startup until listening when activated before the HTTP server is ready', async () => {
  60. const f = fixture(true)
  61. try {
  62. f.node.startBackground()
  63. f.node.startBackground()
  64. for (const start of Object.values(f.starts)) expect(start).toHaveBeenCalledTimes(1)
  65. expect(f.localVm).not.toHaveBeenCalled()
  66. await f.listen()
  67. expect(f.localVm).toHaveBeenCalledTimes(1)
  68. f.node.startBackground()
  69. expect(f.localVm).toHaveBeenCalledTimes(1)
  70. } finally { await f.close() }
  71. })
  72. it('preserves automatic background startup for ordinary servers', async () => {
  73. const f = fixture(false)
  74. try {
  75. for (const start of Object.values(f.starts)) expect(start).toHaveBeenCalledTimes(1)
  76. expect(f.chatStop).toHaveBeenCalledTimes(1)
  77. expect(f.localVm).not.toHaveBeenCalled()
  78. await f.listen()
  79. expect(f.localVm).toHaveBeenCalledTimes(1)
  80. f.node.startBackground()
  81. for (const start of Object.values(f.starts)) expect(start).toHaveBeenCalledTimes(1)
  82. } finally { await f.close() }
  83. })