// // Copyright 2026 The InfiniFlow Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package canvas import ( "context" "testing" "ragflow/internal/agent/runtime" ) // TestCompile_SetupOverrides exercises the full canvas-level wiring: // // canvas.Compile(ctx, dsl, WithSetupOverrides(override)) // // threads the cpnID-keyed override through ctx into // BuildWorkflow → buildNodeBody → applySetupOverrides → mergeSetups, so // each component's factory receives its own merged params["setups"]. Only // the entry for a component's own cpnID applies; components absent from the // override map keep their base params (no spurious "setups" injected). func TestCompile_SetupOverrides(t *testing.T) { captured := map[string]map[string]any{} // component_name -> params received by factory factory := func(name string, params map[string]any) (runtime.Component, error) { // Deep-shallow copy so later mutations don't hide what the // factory actually received. cp := make(map[string]any, len(params)) for k, v := range params { cp[k] = v } captured[name] = cp return &stubComponent{params: cp}, nil } dsl := &Canvas{ Components: map[string]CanvasComponent{ "parser_0": { Obj: CanvasComponentObj{ ComponentName: "Parser", Params: map[string]any{ "setups": map[string]any{ "pdf": map[string]any{ "output_format": "one", "parse_method": "naive", }, "doc": map[string]any{ "output_format": "one", }, }, }, }, Upstream: []string{}, Downstream: []string{"sink_0"}, }, "sink_0": { Obj: CanvasComponentObj{ ComponentName: "Sink", Params: map[string]any{"name": "sink"}, }, Upstream: []string{"parser_0"}, Downstream: []string{}, }, }, Path: []string{"parser_0", "sink_0"}, } // Override is keyed by cpnID. Only "parser_0" is present, so its // "pdf" entry is fully replaced (parse_method dropped) and "docx" is // injected; "doc" survives from the base. "sink_0" is absent and must // keep its base params untouched. override := map[string]any{ "parser_0": map[string]any{ "pdf": map[string]any{ "output_format": "detailed", }, "docx": map[string]any{ "output_format": "one", }, }, } ctx := WithComponentFactory(context.Background(), factory) if _, err := Compile(ctx, dsl, WithSetupOverrides(override)); err != nil { t.Fatalf("Compile: %v", err) } // parser_0: override merged into params["setups"] before the factory. got, ok := captured["Parser"]["setups"].(map[string]any) if !ok { t.Fatalf("Parser factory did not receive a setups map: %#v", captured["Parser"]) } // doc preserved from base. if v, _ := got["doc"].(map[string]any); v == nil || v["output_format"] != "one" { t.Errorf("doc should be preserved from base: %#v", got["doc"]) } // docx injected by the override (was absent in base). if v, _ := got["docx"].(map[string]any); v == nil || v["output_format"] != "one" { t.Errorf("docx injection missing: %#v", got["docx"]) } // pdf: override fully replaces the base entry (shallow merge). pdf, _ := got["pdf"].(map[string]any) if pdf == nil { t.Fatalf("pdf setup missing: %#v", got) } if pdf["output_format"] != "detailed" { t.Errorf("pdf.output_format not overridden: %#v", pdf) } if _, ok := pdf["parse_method"]; ok { t.Errorf("pdf.parse_method should be dropped by shallow merge: %#v", pdf) } // sink_0: absent from the override → no setups key injected. if _, ok := captured["Sink"]["setups"]; ok { t.Errorf("Sink should not receive a setups key: %#v", captured["Sink"]) } }