返回 oh-my-ppt
runtime-capabilities.test.ts
根目录 / tests / unit / ipc / runtime-capabilities.test.ts
1 import fs from 'fs'
2 import path from 'path'
3 import { describe, expect, it } from 'vitest'
4 import { TypedEventBus } from '../../../src/main/agent-runtime'
5 import { createRuntimeEmitters } from '../../../src/main/ipc/runtime/runtime-emitters'
6 import { createSessionProjectResolver } from '../../../src/main/ipc/runtime/session-project'
7 import { createSessionRunStateStore } from '../../../src/main/ipc/runtime/session-run-state'
8
9 describe('IPC runtime capabilities', () => {
10 it('keeps generation on its own narrow context instead of the IPC facade', () => {
11 const generationDirectory = path.resolve('src/main/generation')
12 const sources = fs
13 .readdirSync(generationDirectory)
14 .filter((entry) => entry.endsWith('.ts'))
15 .map((entry) => ({
16 path: path.join(generationDirectory, entry),
17 source: fs.readFileSync(path.join(generationDirectory, entry), 'utf8')
18 }))
19
20 for (const source of sources) {
21 expect(source.source, source.path).not.toContain('IpcContext')
22 }
23 const generationContext = sources.find(({ path: filePath }) => filePath.endsWith('/context.ts'))
24 expect(generationContext?.source).toContain('export type GenerationDbPort')
25 expect(generationContext?.source).toContain('export type GenerationContext')
26 })
27
28 it('keeps session run state separate from lifecycle event emission', () => {
29 const sessionRuns = createSessionRunStateStore()
30 const runtimeEvents = new TypedEventBus()
31 const received: string[] = []
32 runtimeEvents.subscribe({ subscriberId: 'test' }, (event) => received.push(event.type))
33 const emitters = createRuntimeEmitters({
34 mainWindow: {
35 isDestroyed: () => false,
36 isVisible: () => true,
37 show: () => undefined,
38 focus: () => undefined
39 } as never,
40 runtimeEvents,
41 sessionRuns
42 })
43
44 const state = sessionRuns.beginSessionRunState({
45 sessionId: 'session-1',
46 runId: 'run-1',
47 mode: 'generate',
48 totalPages: 2
49 })
50 expect(received).toEqual([])
51
52 emitters.emitSessionRunLifecycle(state)
53 emitters.emitGenerateChunk('session-1', {
54 type: 'page_generated',
55 payload: {
56 runId: 'run-1',
57 stage: 'rendering',
58 pageId: 'page-1',
59 pageNumber: 1,
60 title: 'Overview',
61 html: '<section>Overview</section>'
62 }
63 })
64
65 expect(received).toEqual(['job.started', 'generation.chunk'])
66 expect(state.completedPageKeys).toEqual(['page-1'])
67 expect(state.events[0]).toMatchObject({
68 type: 'page_generated',
69 payload: { html: '' }
70 })
71 })
72
73 it('builds a session snapshot through the project capability', async () => {
74 const db = {
75 getSession: async () => ({ id: 'session-1' }),
76 getProject: async () => ({ id: 'project-1', root_path: '/tmp/session-1' }),
77 listSessionPages: async () => [
78 {
79 file_slug: 'page-1',
80 page_number: 1,
81 title: 'Overview',
82 html_path: 'page-1.html',
83 status: 'completed',
84 error: null
85 },
86 {
87 file_slug: 'page-2',
88 page_number: 2,
89 title: 'Risks',
90 html_path: 'page-2.html',
91 status: 'failed',
92 error: 'model failed'
93 }
94 ]
95 }
96 const project = createSessionProjectResolver({ db: db as never })
97
98 const snapshot = await project.buildSessionGenerationSnapshot(
99 { id: 'session-1', metadata: '{"existing":true}' },
100 { includeHtml: false }
101 )
102
103 expect(snapshot.pages).toEqual(
104 expect.arrayContaining([
105 expect.objectContaining({ pageId: 'page-1', html: '', status: 'completed' }),
106 expect.objectContaining({ pageId: 'page-2', html: '', error: 'model failed' })
107 ])
108 )
109 expect(snapshot.session).toMatchObject({
110 page_count: 2,
111 generated_count: 1,
112 failed_count: 1
113 })
114 expect(JSON.parse(String(snapshot.session?.metadata))).toMatchObject({
115 existing: true,
116 entryMode: 'multi_page',
117 projectId: 'project-1'
118 })
119 })
120 })
121
121 lines TYPESCRIPT