From 5b6d128fa6d1af982e0cebf77d8f1977d8527922 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Tue, 13 Jan 2026 21:05:31 +1300 Subject: [PATCH] test(20-01): add validateWorkflow integration tests - Empty workflow validation - nanoBanana node validation (text required, image optional) - annotation node validation (image or sourceImage required) - output node validation (image required) - Valid workflow scenarios (chains, complex, groups) - Multiple errors reported Co-Authored-By: Claude Opus 4.5 --- .../workflowStore.integration.test.ts | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) diff --git a/src/store/__tests__/workflowStore.integration.test.ts b/src/store/__tests__/workflowStore.integration.test.ts index 1bf93f4a..d10de6ab 100644 --- a/src/store/__tests__/workflowStore.integration.test.ts +++ b/src/store/__tests__/workflowStore.integration.test.ts @@ -429,4 +429,280 @@ describe("workflowStore integration tests", () => { }); }); }); + + describe("validateWorkflow", () => { + describe("Empty workflow", () => { + it("should return invalid with 'Workflow is empty' error", () => { + useWorkflowStore.setState({ + nodes: [], + edges: [], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("Workflow is empty"); + }); + }); + + describe("nanoBanana node validation", () => { + it("should return error when nanoBanana node missing text input", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("nanoBanana-1", "nanoBanana", {}), + ], + edges: [], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(false); + expect(result.errors).toContain('Generate node "nanoBanana-1" missing text input'); + }); + + it("should return valid when text input is connected", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("prompt-1", "prompt", { prompt: "test" }), + createTestNode("nanoBanana-1", "nanoBanana", {}), + ], + edges: [createTestEdge("prompt-1", "nanoBanana-1", "text", "text")], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it("should not require image input (optional)", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("prompt-1", "prompt", { prompt: "test" }), + createTestNode("nanoBanana-1", "nanoBanana", {}), + ], + edges: [createTestEdge("prompt-1", "nanoBanana-1", "text", "text")], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + // Should be valid without image input + expect(result.valid).toBe(true); + }); + + it("should validate multiple nanoBanana nodes independently", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("prompt-1", "prompt", { prompt: "test" }), + createTestNode("nanoBanana-1", "nanoBanana", {}), + createTestNode("nanoBanana-2", "nanoBanana", {}), // No text input + ], + edges: [createTestEdge("prompt-1", "nanoBanana-1", "text", "text")], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(false); + expect(result.errors).toContain('Generate node "nanoBanana-2" missing text input'); + expect(result.errors).not.toContain('Generate node "nanoBanana-1" missing text input'); + }); + }); + + describe("annotation node validation", () => { + it("should return error when no image connected and no sourceImage", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("annotation-1", "annotation", { sourceImage: null }), + ], + edges: [], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(false); + expect(result.errors).toContain('Annotation node "annotation-1" missing image input'); + }); + + it("should return valid when image is connected", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("imageInput-1", "imageInput", { image: "data:image/png;base64,test" }), + createTestNode("annotation-1", "annotation", { sourceImage: null }), + ], + edges: [createTestEdge("imageInput-1", "annotation-1", "image", "image")], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(true); + }); + + it("should return valid when sourceImage is present (manual load)", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("annotation-1", "annotation", { + sourceImage: "data:image/png;base64,manuallyLoadedImage", + }), + ], + edges: [], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(true); + }); + }); + + describe("output node validation", () => { + it("should return error when output node has no image input", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("output-1", "output", {}), + ], + edges: [], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(false); + expect(result.errors).toContain('Output node "output-1" missing image input'); + }); + + it("should return valid when output node has image connected", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("imageInput-1", "imageInput", { image: "data:image/png;base64,test" }), + createTestNode("output-1", "output", {}), + ], + edges: [createTestEdge("imageInput-1", "output-1", "image", "image")], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(true); + }); + }); + + describe("Valid workflow scenarios", () => { + it("should validate simple prompt -> nanoBanana -> output chain", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("prompt-1", "prompt", { prompt: "test prompt" }), + createTestNode("nanoBanana-1", "nanoBanana", {}), + createTestNode("output-1", "output", {}), + ], + edges: [ + createTestEdge("prompt-1", "nanoBanana-1", "text", "text"), + createTestEdge("nanoBanana-1", "output-1", "image", "image"), + ], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it("should validate complex workflow with multiple node types", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("imageInput-1", "imageInput", { image: "data:image/png;base64,test" }), + createTestNode("prompt-1", "prompt", { prompt: "describe this" }), + createTestNode("llmGenerate-1", "llmGenerate", {}), + createTestNode("nanoBanana-1", "nanoBanana", {}), + createTestNode("annotation-1", "annotation", { sourceImage: null }), + createTestNode("output-1", "output", {}), + ], + edges: [ + createTestEdge("imageInput-1", "llmGenerate-1", "image", "image"), + createTestEdge("prompt-1", "llmGenerate-1", "text", "text"), + createTestEdge("llmGenerate-1", "nanoBanana-1", "text", "text"), + createTestEdge("nanoBanana-1", "annotation-1", "image", "image"), + createTestEdge("annotation-1", "output-1", "image", "image"), + ], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it("should validate workflow with groups (groups don't affect validation)", () => { + useWorkflowStore.setState({ + nodes: [ + { ...createTestNode("prompt-1", "prompt", { prompt: "test" }), groupId: "group-1" }, + { ...createTestNode("nanoBanana-1", "nanoBanana", {}), groupId: "group-1" }, + ], + edges: [createTestEdge("prompt-1", "nanoBanana-1", "text", "text")], + groups: { + "group-1": { + id: "group-1", + name: "Test Group", + color: "neutral", + position: { x: 0, y: 0 }, + size: { width: 400, height: 400 }, + }, + }, + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(true); + }); + + it("should allow nodes that don't require validation (imageInput, prompt)", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("imageInput-1", "imageInput", { image: null }), + createTestNode("prompt-1", "prompt", { prompt: "" }), + ], + edges: [], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + // These nodes don't have validation rules, so workflow is valid + expect(result.valid).toBe(true); + }); + }); + + describe("Multiple validation errors", () => { + it("should report all validation errors, not just the first", () => { + useWorkflowStore.setState({ + nodes: [ + createTestNode("nanoBanana-1", "nanoBanana", {}), + createTestNode("nanoBanana-2", "nanoBanana", {}), + createTestNode("annotation-1", "annotation", { sourceImage: null }), + createTestNode("output-1", "output", {}), + ], + edges: [], + }); + + const store = useWorkflowStore.getState(); + const result = store.validateWorkflow(); + + expect(result.valid).toBe(false); + expect(result.errors.length).toBeGreaterThanOrEqual(4); + expect(result.errors).toContain('Generate node "nanoBanana-1" missing text input'); + expect(result.errors).toContain('Generate node "nanoBanana-2" missing text input'); + expect(result.errors).toContain('Annotation node "annotation-1" missing image input'); + expect(result.errors).toContain('Output node "output-1" missing image input'); + }); + }); + }); });