@@ -640,6 +640,70 @@ async def test_get_operation_lineage_with_symlinks(
640640 }
641641
642642
643+ async def test_get_operation_lineage_with_symlink_without_input_output (
644+ test_client : AsyncClient ,
645+ async_session : AsyncSession ,
646+ lineage_with_unconnected_symlinks : LineageResult ,
647+ mocked_user : MockedUser ,
648+ ):
649+ lineage = lineage_with_unconnected_symlinks
650+ operation = lineage .operations [0 ]
651+
652+ inputs = [input for input in lineage .inputs if input .operation_id == operation .id ]
653+ assert inputs
654+
655+ outputs = [output for output in lineage .outputs if output .operation_id == operation .id ]
656+ assert outputs
657+
658+ dataset_ids = {input .dataset_id for input in inputs } | {output .dataset_id for output in outputs }
659+ assert dataset_ids
660+
661+ datasets = [dataset for dataset in lineage .datasets if dataset .id in dataset_ids ]
662+ assert datasets
663+
664+ run = next (run for run in lineage .runs if run .id == operation .run_id )
665+ job = next (job for job in lineage .jobs if job .id == run .job_id )
666+
667+ [job ] = await enrich_jobs ([job ], async_session )
668+ [run ] = await enrich_runs ([run ], async_session )
669+ datasets = await enrich_datasets (datasets , async_session )
670+
671+ response = await test_client .get (
672+ "v1/operations/lineage" ,
673+ headers = {"Authorization" : f"Bearer { mocked_user .access_token } " },
674+ params = {
675+ "since" : run .created_at .isoformat (),
676+ "start_node_id" : str (operation .id ),
677+ },
678+ )
679+
680+ assert response .status_code == HTTPStatus .OK , response .json ()
681+ assert response .json () == {
682+ "relations" : {
683+ "parents" : run_parents_to_json ([run ]) + operation_parents_to_json ([operation ]),
684+ "symlinks" : [], # symlinks without inputs/outputs are excluded
685+ "inputs" : [
686+ * inputs_to_json (merge_io_by_jobs (inputs ), granularity = "JOB" ),
687+ * inputs_to_json (inputs , granularity = "OPERATION" ),
688+ * inputs_to_json (merge_io_by_runs (inputs ), granularity = "RUN" ),
689+ ],
690+ "outputs" : [
691+ * outputs_to_json (merge_io_by_jobs (outputs ), granularity = "JOB" ),
692+ * outputs_to_json (outputs , granularity = "OPERATION" ),
693+ * outputs_to_json (merge_io_by_runs (outputs ), granularity = "RUN" ),
694+ ],
695+ "direct_column_lineage" : [],
696+ "indirect_column_lineage" : [],
697+ },
698+ "nodes" : {
699+ "datasets" : datasets_to_json (datasets ),
700+ "jobs" : jobs_to_json ([job ]),
701+ "runs" : runs_to_json ([run ]),
702+ "operations" : operations_to_json ([operation ]),
703+ },
704+ }
705+
706+
643707async def test_get_operation_lineage_with_empty_io_stats_and_schema (
644708 test_client : AsyncClient ,
645709 async_session : AsyncSession ,
0 commit comments