UDT_CONNECTION_VALIDATION_FIXTURES

UDT Connection-Validation Fixtures — Declarative, Cross-Language

Date: 2026-05-23 Parent plan: USER_DEFINED_TOOL_STEP_VALIDATION (Phases A–E landed) Companion docs:


1. Gap

Parent plan §9.8 listed UDT-feeds-regular-tool, regular-tool-feeds-UDT, data_collection-typed UDT output, and UDT-in-subworkflow as expected fixtures. Only the subworkflow case landed (test_inline_udt_workflows.py::test_udt_inside_format2_subworkflow_resolved). The rest are absent.

In addition, the existing inline-UDT connection tests:

The connection graph “just works” theory from §5.6 of the parent plan is plausible (the same ResolvedStep shape feeds the same downstream code) but unlocked by tests.

2. Existing declarative seam — already cross-language

Both repos already have a parallel, declarative, file-based connection-validation corpus:

SideDriverFixture dirExpected sidecar
Pythontest/unit/tool_util/workflow_state/test_connection_workflows.pytest/unit/tool_util/workflow_state/connection_workflows/*.gxwf.ymlconnection_workflows/expected/<stem>.yml
TypeScriptpackages/connection-validation/test/connection-workflows.test.tspackages/core/test/fixtures/connection_workflows/*.gxwf.ymlconnection_workflows/expected/<stem>.yml

Conventions both sides honor:

This is the right home for UDT connection fixtures. Sidecar YAMLs are language-agnostic data, not test code; one fixture file locks the same behavior on both sides.

3. TS-side parity prerequisite

loadParsedToolCache only reads JSON tool dumps; it does not parse step.tool_representation. To run UDT-bearing fixtures, the TS GetToolInfo test helper needs an inline-tool fast path equivalent to Python’s resolve_for_step — parse the YAML representation inline rather than expecting a cache hit.

This is TS-port work, but the fixture layout is identical regardless. Recommended ordering:

  1. Land Python fixtures + sidecars (this plan).
  2. File the inline-tool resolver upgrade as a TS-side task with the Python fixtures as the parity target.
  3. TS port copies the fixtures + sidecars verbatim; tests go green when the resolver lands.

The fixtures must be self-contained (no tool_uuid-only references); every UDT step carries its tool_representation/run body inline.

4. Fixture catalog

All paths under test/unit/tool_util/workflow_state/connection_workflows/ (Python) and packages/core/test/fixtures/connection_workflows/ (TS).

UDT body used throughout is the cat_user_defined shape that already exists at test/functional/tools/cat_user_defined.yml — keeps the offline corpus coupled to the live UDT framework test. Collection-output and format-source variants extend that body inline in the fixture.

4.1 ok_ fixtures (must validate)

FixtureShapeLocks
ok_udt_to_regular_dataset.gxwf.ymlinput → UDT(data) → cat1(data)UDT data output feeds regular tool data input. Baseline producer-side parity.
ok_regular_to_udt_dataset.gxwf.ymlinput → cat1(data) → UDT(data)UDT data input accepts regular tool data output. Baseline consumer-side parity.
ok_udt_chain.gxwf.ymlinput → UDT → UDTUDT-to-UDT chain. Exercises the inline resolver twice in one graph.
ok_udt_collection_output.gxwf.ymlinput → UDT(type: data_collection, collection_type: list, discover_datasets) → list-consumerUDT collection output declaration propagates collection_type into ResolvedOutputType.
ok_udt_collection_input.gxwf.ymllist input → UDT(input1: type: data_collection, collection_type: list) → list outputUDT collection-typed input resolves without map-over confusion.
ok_udt_map_over_list.gxwf.ymllist input → UDT(data input) → list output via implicit map-overeffective_map_over propagation through a UDT producer. Step’s map_over field in the report should be "list".
ok_udt_format_source_propagates.gxwf.ymlinput(format: bed) → UDT with output: format_source: input1 → format-aware consumerAlready covered structurally by test_inline_udt_output_format_source_resolves_to_inline_input; promoting to declarative locks the report-level resolution.
ok_udt_in_subworkflow.gxwf.ymlparent → subworkflow{ UDT } → parent consumerPromotes the in-tree test_udt_inside_format2_subworkflow_resolved to declarative form.

4.2 fail_ fixtures (must not validate)

FixtureShapeLocks
fail_udt_dataset_to_collection_consumer.gxwf.ymlUDT produces data → consumer requires data_collectionReal type-mismatch through UDT producer. The first test that exercises an "invalid" status on a UDT-producer connection.
fail_collection_to_udt_dataset.gxwf.ymllist collection → UDT data input (no map-over declared)Pre-resolved collection cannot feed UDT scalar input without implicit map-over fallback.
fail_udt_consumer_diagnostic_on_consumer.gxwf.ymlSame shape as fail_udt_dataset_to_collection_consumer with sidecar asserting diagnostic lands on consumer stepReplaces the no-op test_inline_udt_connection_diagnostic_attributed_to_consumer with a real assertion: step_results[<consumer>].connections[0].status == "invalid" and step_results[<producer>].connections == [].

4.3 Sidecar expectations — what to assert

For each fixture, the expected/<stem>.yml must include:

These assert against the existing ConnectionValidationReport shape — no new report fields required.

5. Functional-tool dependencies

Most fixtures reference existing functional tools (cat1, multi_data_param, collection_paired_test, etc.). Two cases need extension:

6. Cross-language fixture sync

Two options for keeping the parallel directories in lockstep:

Drop fixtures and sidecars into the Python tree first. Open a sibling PR in galaxy-tool-util-ts that cps the same files into packages/core/test/fixtures/connection_workflows/. CI runs both suites.

Drift risk: Real. Mitigation: a small scripts/check_fixture_parity.py (committed in both repos) that diffs the two fixture directories and fails CI if they differ. Run as a pre-commit hook on both sides.

Option B — Canonical source, generated copies

Move fixtures into a third location (gxformat2’s examples/ tree is the obvious candidate — already cross-language and depended on by both). Each side’s test driver loads from the gxformat2 install.

Cost: Touches a third repo, bumps a dep pin, slows iteration. Worth it long-term; overkill for landing this gap.

Recommendation: Option A + the parity script. Promote to Option B in a separate PR once the fixture set stabilizes.

7. Test driver changes

test_connection_workflows.py auto-discovers by glob — no driver change needed once fixtures land. Same on TS side (for (const f of fixtures)).

The only edit needed: delete test_inline_udt_connection_diagnostic_attributed_to_consumer from test_inline_udt_workflows.py once fail_udt_consumer_diagnostic_on_consumer.gxwf.yml lands. It’s currently a no-op and the fixture supersedes it.

The other test_inline_udt_workflows.py connection tests (test_connection_graph_inline_outputs_resolved, test_connection_validation_inline_udt_succeeds, test_inline_udt_output_format_source_resolves_to_inline_input) can stay — they test the graph layer directly with Python-dict synthesis. Fixtures lock end-to-end behavior; unit tests lock structural invariants. Both have value.

8. Phasing

Phase A — corpus extension, Python side

  1. Add the 8 ok_ + 3 fail_ fixtures from §4 to test/unit/tool_util/workflow_state/connection_workflows/.
  2. Author sidecar expected/<stem>.yml for each per §4.3.
  3. Survey test/functional/tools/ for an appropriate list-collection consumer; add one only if none exists.
  4. Run pytest test/unit/tool_util/workflow_state/test_connection_workflows.py to confirm all ok_ pass and fail_ fail.
  5. Delete the no-op test_inline_udt_connection_diagnostic_attributed_to_consumer.

Phase B — TS port parity

  1. File issue in galaxy-tool-util-ts referencing this plan: TS connection-validation needs an inline-tool resolver path in its GetToolInfo test helper.
  2. Once landed there, cp the 11 fixtures + sidecars into packages/core/test/fixtures/connection_workflows/.
  3. Commit the scripts/check_fixture_parity.py companion script (or a make check-fixture-parity target) in both repos.

Phase C — promote to canonical (optional, deferred)

Move fixtures to gxformat2’s examples/connection_workflows/ (or similar). Bump deps. Out of scope until corpus stabilizes.

9. Open questions