Skip to content

Commit 0371a2a

Browse files
committed
add tests - fix history_v2/:prompt_id fmt
1 parent 9a6a1c1 commit 0371a2a

File tree

4 files changed

+313
-10
lines changed

4 files changed

+313
-10
lines changed

src/components/sidebar/tabs/QueueSidebarTab.vue

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,11 @@ const menuItems = computed<MenuItem[]>(() => {
214214
void workflowService.loadTaskWorkflow(menuTargetTask.value)
215215
}
216216
},
217-
disabled:
218-
!menuTargetTask.value?.workflow &&
219-
!(
220-
menuTargetTask.value?.isHistory &&
221-
menuTargetTask.value?.prompt.prompt_id
222-
)
217+
disabled: !(
218+
menuTargetTask.value?.workflow ||
219+
(menuTargetTask.value?.isHistory &&
220+
menuTargetTask.value?.prompt.prompt_id)
221+
)
223222
},
224223
{
225224
label: t('g.goToNode'),

src/scripts/api.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -743,13 +743,13 @@ export class ComfyApi extends EventTarget {
743743
const json = await res.json()
744744

745745
// The /history_v2/{prompt_id} endpoint returns data for a specific prompt
746-
// The response format is: { prompt_id: { prompt: [...], outputs: {...}, status: {...} } }
746+
// The response format is: { prompt_id: { prompt: {priority, prompt_id, prompt, extra_data, outputs_to_execute}, outputs: {...}, status: {...} } }
747747
const historyItem = json[prompt_id]
748748
if (!historyItem) return null
749749

750-
// Extract workflow from the prompt array
751-
// prompt[3] contains extra_data which has extra_pnginfo.workflow
752-
const workflow = historyItem.prompt?.[3]?.extra_pnginfo?.workflow
750+
// Extract workflow from the prompt object
751+
// prompt.extra_data contains extra_pnginfo.workflow
752+
const workflow = historyItem.prompt?.extra_data?.extra_pnginfo?.workflow
753753
return workflow || null
754754
} catch (error) {
755755
console.error(`Failed to fetch workflow for prompt ${prompt_id}:`, error)

tests-ui/tests/scripts/api.test.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
HistoryResponse,
55
RawHistoryItem
66
} from '../../../src/schemas/apiSchema'
7+
import type { ComfyWorkflowJSON } from '../../../src/schemas/comfyWorkflowSchema'
78
import { ComfyApi } from '../../../src/scripts/api'
89

910
describe('ComfyApi getHistory', () => {
@@ -124,3 +125,124 @@ describe('ComfyApi getHistory', () => {
124125
})
125126
})
126127
})
128+
129+
describe('ComfyApi getWorkflowFromHistory', () => {
130+
let api: ComfyApi
131+
132+
beforeEach(() => {
133+
api = new ComfyApi()
134+
})
135+
136+
const mockWorkflow: ComfyWorkflowJSON = {
137+
last_node_id: 1,
138+
last_link_id: 0,
139+
nodes: [],
140+
links: [],
141+
groups: [],
142+
config: {},
143+
extra: {},
144+
version: 0.4
145+
}
146+
147+
it('should fetch workflow data for a specific prompt', async () => {
148+
const promptId = 'test_prompt_id'
149+
const mockResponse = {
150+
[promptId]: {
151+
prompt: {
152+
priority: 0,
153+
prompt_id: promptId,
154+
extra_data: {
155+
extra_pnginfo: {
156+
workflow: mockWorkflow
157+
}
158+
}
159+
},
160+
outputs: {},
161+
status: {
162+
status_str: 'success',
163+
completed: true,
164+
messages: []
165+
}
166+
}
167+
}
168+
169+
const mockFetchApi = vi.fn().mockResolvedValue({
170+
json: vi.fn().mockResolvedValue(mockResponse)
171+
})
172+
api.fetchApi = mockFetchApi
173+
174+
const result = await api.getWorkflowFromHistory(promptId)
175+
176+
expect(mockFetchApi).toHaveBeenCalledWith(`/history_v2/${promptId}`)
177+
expect(result).toEqual(mockWorkflow)
178+
})
179+
180+
it('should return null when prompt_id is not found', async () => {
181+
const promptId = 'non_existent_prompt'
182+
const mockResponse = {}
183+
184+
const mockFetchApi = vi.fn().mockResolvedValue({
185+
json: vi.fn().mockResolvedValue(mockResponse)
186+
})
187+
api.fetchApi = mockFetchApi
188+
189+
const result = await api.getWorkflowFromHistory(promptId)
190+
191+
expect(mockFetchApi).toHaveBeenCalledWith(`/history_v2/${promptId}`)
192+
expect(result).toBeNull()
193+
})
194+
195+
it('should return null when workflow data is missing', async () => {
196+
const promptId = 'test_prompt_id'
197+
const mockResponse = {
198+
[promptId]: {
199+
prompt: {
200+
priority: 0,
201+
prompt_id: promptId,
202+
extra_data: {}
203+
},
204+
outputs: {},
205+
status: {
206+
status_str: 'success',
207+
completed: true,
208+
messages: []
209+
}
210+
}
211+
}
212+
213+
const mockFetchApi = vi.fn().mockResolvedValue({
214+
json: vi.fn().mockResolvedValue(mockResponse)
215+
})
216+
api.fetchApi = mockFetchApi
217+
218+
const result = await api.getWorkflowFromHistory(promptId)
219+
220+
expect(result).toBeNull()
221+
})
222+
223+
it('should handle API errors gracefully', async () => {
224+
const promptId = 'test_prompt_id'
225+
const mockFetchApi = vi.fn().mockRejectedValue(new Error('Network error'))
226+
api.fetchApi = mockFetchApi
227+
228+
const result = await api.getWorkflowFromHistory(promptId)
229+
230+
expect(result).toBeNull()
231+
})
232+
233+
it('should handle malformed response gracefully', async () => {
234+
const promptId = 'test_prompt_id'
235+
const mockResponse = {
236+
[promptId]: null
237+
}
238+
239+
const mockFetchApi = vi.fn().mockResolvedValue({
240+
json: vi.fn().mockResolvedValue(mockResponse)
241+
})
242+
api.fetchApi = mockFetchApi
243+
244+
const result = await api.getWorkflowFromHistory(promptId)
245+
246+
expect(result).toBeNull()
247+
})
248+
})
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { createPinia, setActivePinia } from 'pinia'
2+
import { beforeEach, describe, expect, it, vi } from 'vitest'
3+
import { toRaw } from 'vue'
4+
5+
import type { ComfyWorkflowJSON } from '../../../src/schemas/comfyWorkflowSchema'
6+
import { app } from '../../../src/scripts/app'
7+
import { useWorkflowService } from '../../../src/services/workflowService'
8+
import { TaskItemImpl } from '../../../src/stores/queueStore'
9+
10+
const mockGetWorkflowFromHistory = vi.fn()
11+
vi.mock('../../../src/scripts/api', () => ({
12+
api: {
13+
getWorkflowFromHistory: mockGetWorkflowFromHistory
14+
}
15+
}))
16+
vi.mock('../../../src/scripts/app', () => ({
17+
app: {
18+
loadGraphData: vi.fn(),
19+
nodeOutputs: {}
20+
}
21+
}))
22+
vi.mock('vue', async () => {
23+
const actual = await vi.importActual('vue')
24+
return {
25+
...actual,
26+
toRaw: vi.fn((obj) => obj)
27+
}
28+
})
29+
30+
describe('workflowService', () => {
31+
let workflowService: ReturnType<typeof useWorkflowService>
32+
33+
beforeEach(() => {
34+
vi.clearAllMocks()
35+
setActivePinia(createPinia())
36+
workflowService = useWorkflowService()
37+
})
38+
39+
describe('loadTaskWorkflow', () => {
40+
const mockWorkflow: ComfyWorkflowJSON = {
41+
last_node_id: 1,
42+
last_link_id: 0,
43+
nodes: [],
44+
links: [],
45+
groups: [],
46+
config: {},
47+
extra: {},
48+
version: 0.4
49+
}
50+
51+
const mockOutputs = {
52+
'node-1': {
53+
images: [{ filename: 'output.png', type: 'output' as const }]
54+
}
55+
}
56+
57+
it('should load workflow directly for queue items', async () => {
58+
const queueTask = new TaskItemImpl(
59+
'Pending',
60+
{
61+
priority: 0,
62+
prompt_id: 'queue_prompt_id',
63+
extra_data: {
64+
client_id: 'test_client',
65+
extra_pnginfo: { workflow: mockWorkflow }
66+
}
67+
},
68+
{ status_str: 'success', messages: [], completed: true },
69+
mockOutputs
70+
)
71+
72+
await workflowService.loadTaskWorkflow(queueTask)
73+
74+
expect(mockGetWorkflowFromHistory).not.toHaveBeenCalled()
75+
expect(app.loadGraphData).toHaveBeenCalledWith(mockWorkflow)
76+
expect(app.nodeOutputs).toEqual(mockOutputs)
77+
})
78+
79+
it('should fetch workflow from API for history items', async () => {
80+
const historyTask = new TaskItemImpl(
81+
'History',
82+
{
83+
priority: 0,
84+
prompt_id: 'history_prompt_id',
85+
extra_data: { client_id: 'test_client' }
86+
},
87+
{ status_str: 'success', messages: [], completed: true },
88+
mockOutputs
89+
)
90+
91+
mockGetWorkflowFromHistory.mockResolvedValue(mockWorkflow)
92+
93+
await workflowService.loadTaskWorkflow(historyTask)
94+
95+
expect(mockGetWorkflowFromHistory).toHaveBeenCalledWith(
96+
'history_prompt_id'
97+
)
98+
expect(app.loadGraphData).toHaveBeenCalledWith(mockWorkflow)
99+
expect(app.nodeOutputs).toEqual(mockOutputs)
100+
})
101+
102+
it('should handle missing workflow data for history items', async () => {
103+
const historyTask = new TaskItemImpl(
104+
'History',
105+
{
106+
priority: 0,
107+
prompt_id: 'history_prompt_id',
108+
extra_data: { client_id: 'test_client' }
109+
},
110+
{ status_str: 'success', messages: [], completed: true },
111+
mockOutputs
112+
)
113+
114+
mockGetWorkflowFromHistory.mockResolvedValue(null)
115+
116+
await workflowService.loadTaskWorkflow(historyTask)
117+
118+
expect(mockGetWorkflowFromHistory).toHaveBeenCalledWith(
119+
'history_prompt_id'
120+
)
121+
expect(app.loadGraphData).not.toHaveBeenCalled()
122+
expect(app.nodeOutputs).not.toEqual(mockOutputs)
123+
})
124+
125+
it('should not fetch workflow for history items without prompt_id', async () => {
126+
const historyTask = new TaskItemImpl(
127+
'History',
128+
{
129+
priority: 0,
130+
prompt_id: '',
131+
extra_data: { client_id: 'test_client' }
132+
},
133+
{ status_str: 'success', messages: [], completed: true },
134+
mockOutputs
135+
)
136+
137+
await workflowService.loadTaskWorkflow(historyTask)
138+
139+
expect(mockGetWorkflowFromHistory).not.toHaveBeenCalled()
140+
expect(app.loadGraphData).not.toHaveBeenCalled()
141+
})
142+
143+
it('should handle tasks without any workflow data', async () => {
144+
const task = new TaskItemImpl(
145+
'Pending',
146+
{
147+
priority: 0,
148+
prompt_id: 'queue_prompt_id',
149+
extra_data: { client_id: 'test_client' }
150+
},
151+
{ status_str: 'success', messages: [], completed: true },
152+
mockOutputs
153+
)
154+
155+
await workflowService.loadTaskWorkflow(task)
156+
157+
expect(app.loadGraphData).not.toHaveBeenCalled()
158+
expect(app.nodeOutputs).not.toEqual(mockOutputs)
159+
})
160+
161+
it('should use toRaw for workflow and outputs', async () => {
162+
const queueTask = new TaskItemImpl(
163+
'Pending',
164+
{
165+
priority: 0,
166+
prompt_id: 'queue_prompt_id',
167+
extra_data: {
168+
client_id: 'test_client',
169+
extra_pnginfo: { workflow: mockWorkflow }
170+
}
171+
},
172+
{ status_str: 'success', messages: [], completed: true },
173+
mockOutputs
174+
)
175+
176+
await workflowService.loadTaskWorkflow(queueTask)
177+
178+
expect(toRaw).toHaveBeenCalledWith(mockWorkflow)
179+
expect(toRaw).toHaveBeenCalledWith(mockOutputs)
180+
})
181+
})
182+
})

0 commit comments

Comments
 (0)