From 74ac4fe0bf31acad251fd4a93881d9c4108bb401 Mon Sep 17 00:00:00 2001 From: suyashj1231 <149845903+suyashj1231@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:36:31 -0700 Subject: [PATCH] test(workflow-graph): cover isSink and isPythonUdf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both helpers gate real behaviour — isSink decides which operators the result panel renders and which ones the operator menu lets you cache, isPythonUdf decides whether the console and the UDF parameter sync run — but neither had any test. The two use different matching rules, and the tests pin both down: isSink case-insensitive substring match on "sink" isPythonUdf exact membership in the three V2 UDF type constants The substring rule means an unrelated operator whose type merely contains "sink" is reported as a sink; that case is covered explicitly so the looseness is recorded rather than discovered later. On the other side, "PythonUDF" (the legacy non-V2 type, already in the mock fixtures) is not a Python UDF by this helper, which is easy to misread at a call site, so it is asserted directly. Closes #6674 Claude-Session: https://claude.ai/code/session_01EeaEYRdhRYWL7ya7w8LJux --- .../model/workflow-graph.spec.ts | 97 ++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.spec.ts b/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.spec.ts index 41942cd7a5c..a1f0cf9607d 100644 --- a/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.spec.ts +++ b/frontend/src/app/workspace/service/workflow-graph/model/workflow-graph.spec.ts @@ -19,7 +19,9 @@ import { mockCommentBox, + mockJavaUDFPredicate, mockMultiInputOutputPredicate, + mockPythonUDFPredicate, mockResultPredicate, mockScanPredicate, mockScanResultLink, @@ -27,9 +29,38 @@ import { mockSentimentPredicate, mockSentimentResultLink, } from "./mock-workflow-data"; -import { WorkflowGraph } from "./workflow-graph"; +import { + DUAL_INPUT_PORTS_PYTHON_UDF_V2_OP_TYPE, + isPythonUdf, + isSink, + PYTHON_UDF_SOURCE_V2_OP_TYPE, + PYTHON_UDF_V2_OP_TYPE, + WorkflowGraph, +} from "./workflow-graph"; import { Observable } from "rxjs"; -import { Comment, OperatorLink, PortDescription, PortProperty } from "../../../types/workflow-common.interface"; +import { + Comment, + OperatorLink, + OperatorPredicate, + PortDescription, + PortProperty, +} from "../../../types/workflow-common.interface"; + +/** + * Builds a minimal operator whose only interesting property is its type, since + * isSink and isPythonUdf look at nothing else. + */ +function operatorOfType(operatorType: string): OperatorPredicate { + return { + operatorID: `op-${operatorType}`, + operatorType, + operatorVersion: "v1", + operatorProperties: {}, + inputPorts: [], + outputPorts: [], + showAdvanced: false, + }; +} describe("WorkflowGraph", () => { let workflowGraph: WorkflowGraph; @@ -830,3 +861,65 @@ describe("WorkflowGraph", () => { }); }); }); + +describe("isSink", () => { + it("should identify the view-result operator as a sink", () => { + expect(isSink(mockResultPredicate)).toBe(true); + }); + + it("should not treat non-sink operators as sinks", () => { + expect(isSink(mockScanPredicate)).toBe(false); + expect(isSink(mockSentimentPredicate)).toBe(false); + expect(isSink(mockPythonUDFPredicate)).toBe(false); + }); + + it("should match the substring regardless of case", () => { + for (const operatorType of ["Sink", "sink", "SINK", "CsvFileSink", "sinkOperator"]) { + expect(isSink(operatorOfType(operatorType))).toBe(true); + } + }); + + it("should match on substring, so an unrelated type containing 'sink' also counts", () => { + // Documents the current substring behaviour: matching is not anchored to the + // end of the type name, so any operator whose type merely contains "sink" + // is reported as a sink. + expect(isSink(operatorOfType("SinkholeDetector"))).toBe(true); + }); + + it("should return false for a type that only partially overlaps 'sink'", () => { + for (const operatorType of ["Sin", "Ink", "Snik", ""]) { + expect(isSink(operatorOfType(operatorType))).toBe(false); + } + }); +}); + +describe("isPythonUdf", () => { + it("should identify every Python UDF operator type", () => { + for (const operatorType of [ + PYTHON_UDF_V2_OP_TYPE, + PYTHON_UDF_SOURCE_V2_OP_TYPE, + DUAL_INPUT_PORTS_PYTHON_UDF_V2_OP_TYPE, + ]) { + expect(isPythonUdf(operatorOfType(operatorType))).toBe(true); + } + }); + + it("should not treat the legacy non-V2 PythonUDF type as a Python UDF", () => { + // mockPythonUDFPredicate is operatorType "PythonUDF", which is not one of the + // three V2 types the helper matches. + expect(isPythonUdf(mockPythonUDFPredicate)).toBe(false); + }); + + it("should not treat other UDF or non-UDF operators as Python UDFs", () => { + expect(isPythonUdf(mockJavaUDFPredicate)).toBe(false); + expect(isPythonUdf(mockScanPredicate)).toBe(false); + expect(isPythonUdf(mockResultPredicate)).toBe(false); + }); + + it("should match the operator type exactly, unlike isSink", () => { + // Exact membership, so neither a different case nor a superstring matches. + for (const operatorType of ["pythonudfv2", "PYTHONUDFV2", `My${PYTHON_UDF_V2_OP_TYPE}`, ""]) { + expect(isPythonUdf(operatorOfType(operatorType))).toBe(false); + } + }); +});