|
|
|
@ -2307,4 +2307,135 @@ describe("workflowStore integration tests", () => { |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
describe("Race condition prevention", () => { |
|
|
|
let mockFetch: ReturnType<typeof vi.fn>; |
|
|
|
|
|
|
|
beforeEach(() => { |
|
|
|
mockFetch = vi.fn().mockResolvedValue({ |
|
|
|
ok: true, |
|
|
|
json: () => Promise.resolve({ success: true, image: "data:image/png;base64,generated" }), |
|
|
|
text: () => Promise.resolve(""), |
|
|
|
}); |
|
|
|
vi.stubGlobal("fetch", mockFetch); |
|
|
|
}); |
|
|
|
|
|
|
|
afterEach(() => { |
|
|
|
vi.unstubAllGlobals(); |
|
|
|
}); |
|
|
|
|
|
|
|
it("should only execute nodes once when executeWorkflow is called concurrently", async () => { |
|
|
|
useWorkflowStore.setState({ |
|
|
|
nodes: [ |
|
|
|
createTestNode("prompt-1", "prompt", { prompt: "test" }), |
|
|
|
createTestNode("nanoBanana-1", "nanoBanana", { |
|
|
|
aspectRatio: "1:1", |
|
|
|
resolution: "1K", |
|
|
|
model: "nano-banana", |
|
|
|
}), |
|
|
|
], |
|
|
|
edges: [ |
|
|
|
createTestEdge("prompt-1", "nanoBanana-1", "text", "text"), |
|
|
|
], |
|
|
|
}); |
|
|
|
|
|
|
|
const store = useWorkflowStore.getState(); |
|
|
|
|
|
|
|
// Fire two calls back-to-back without awaiting the first
|
|
|
|
const p1 = store.executeWorkflow(); |
|
|
|
const p2 = store.executeWorkflow(); |
|
|
|
await Promise.all([p1, p2]); |
|
|
|
|
|
|
|
// Only one execution should have reached fetch (one nanoBanana node)
|
|
|
|
expect(mockFetch).toHaveBeenCalledTimes(1); |
|
|
|
}); |
|
|
|
|
|
|
|
it("should set isRunning synchronously before any await", async () => { |
|
|
|
useWorkflowStore.setState({ |
|
|
|
nodes: [ |
|
|
|
createTestNode("prompt-1", "prompt", { prompt: "test" }), |
|
|
|
createTestNode("nanoBanana-1", "nanoBanana", { |
|
|
|
aspectRatio: "1:1", |
|
|
|
resolution: "1K", |
|
|
|
model: "nano-banana", |
|
|
|
}), |
|
|
|
], |
|
|
|
edges: [ |
|
|
|
createTestEdge("prompt-1", "nanoBanana-1", "text", "text"), |
|
|
|
], |
|
|
|
}); |
|
|
|
|
|
|
|
const store = useWorkflowStore.getState(); |
|
|
|
expect(useWorkflowStore.getState().isRunning).toBe(false); |
|
|
|
|
|
|
|
// Call without awaiting — isRunning should be true synchronously
|
|
|
|
const promise = store.executeWorkflow(); |
|
|
|
expect(useWorkflowStore.getState().isRunning).toBe(true); |
|
|
|
|
|
|
|
await promise; |
|
|
|
}); |
|
|
|
|
|
|
|
it("should only execute once when regenerateNode is called concurrently", async () => { |
|
|
|
useWorkflowStore.setState({ |
|
|
|
nodes: [ |
|
|
|
createTestNode("prompt-1", "prompt", { prompt: "test" }), |
|
|
|
createTestNode("nanoBanana-1", "nanoBanana", { |
|
|
|
aspectRatio: "1:1", |
|
|
|
resolution: "1K", |
|
|
|
model: "nano-banana", |
|
|
|
inputImages: [], |
|
|
|
outputImage: "data:image/png;base64,previous", |
|
|
|
}), |
|
|
|
], |
|
|
|
edges: [ |
|
|
|
createTestEdge("prompt-1", "nanoBanana-1", "text", "text"), |
|
|
|
], |
|
|
|
}); |
|
|
|
|
|
|
|
const store = useWorkflowStore.getState(); |
|
|
|
|
|
|
|
// Fire two calls back-to-back — second should be blocked by isRunning
|
|
|
|
const p1 = store.regenerateNode("nanoBanana-1"); |
|
|
|
const p2 = store.regenerateNode("nanoBanana-1"); |
|
|
|
await Promise.all([p1, p2]); |
|
|
|
|
|
|
|
expect(mockFetch).toHaveBeenCalledTimes(1); |
|
|
|
}); |
|
|
|
|
|
|
|
it("should execute each node exactly once with multiple disconnected nodes", async () => { |
|
|
|
useWorkflowStore.setState({ |
|
|
|
nodes: [ |
|
|
|
createTestNode("prompt-1", "prompt", { prompt: "a" }), |
|
|
|
createTestNode("prompt-2", "prompt", { prompt: "b" }), |
|
|
|
createTestNode("prompt-3", "prompt", { prompt: "c" }), |
|
|
|
createTestNode("nanoBanana-1", "nanoBanana", { |
|
|
|
aspectRatio: "1:1", |
|
|
|
resolution: "1K", |
|
|
|
model: "nano-banana", |
|
|
|
}), |
|
|
|
createTestNode("nanoBanana-2", "nanoBanana", { |
|
|
|
aspectRatio: "1:1", |
|
|
|
resolution: "1K", |
|
|
|
model: "nano-banana", |
|
|
|
}), |
|
|
|
createTestNode("nanoBanana-3", "nanoBanana", { |
|
|
|
aspectRatio: "1:1", |
|
|
|
resolution: "1K", |
|
|
|
model: "nano-banana", |
|
|
|
}), |
|
|
|
], |
|
|
|
edges: [ |
|
|
|
createTestEdge("prompt-1", "nanoBanana-1", "text", "text"), |
|
|
|
createTestEdge("prompt-2", "nanoBanana-2", "text", "text"), |
|
|
|
createTestEdge("prompt-3", "nanoBanana-3", "text", "text"), |
|
|
|
], |
|
|
|
}); |
|
|
|
|
|
|
|
const store = useWorkflowStore.getState(); |
|
|
|
await store.executeWorkflow(); |
|
|
|
|
|
|
|
// Exactly 3 fetch calls — one per nanoBanana node
|
|
|
|
expect(mockFetch).toHaveBeenCalledTimes(3); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|