@@ -309,6 +309,150 @@ def test_import_bundle_with_absolute_recorded_path(tdt_manager, tmp_path, table_
309309 assert os .path .exists (os .path .join (loc_target , f"{ index } .pkl" ))
310310
311311
312+ def prepare_split_prefix_output_and_description (
313+ tmp_path ,
314+ table_name = "testalpha" ,
315+ path_column = "path" ,
316+ store_leaf = None ,
317+ compute_leaf = None ,
318+ recorded_relative_path = None ,
319+ ):
320+ """Model the object-store vs job-working-dir prefix split.
321+
322+ In production ``write_bundle`` runs on the job handler, where
323+ ``dataset.extra_files_path`` resolves to the object-store extra-files dir,
324+ while the data manager baked its absolute ``path`` on the compute node under
325+ the transient job working dir. The directories differ in every prefix
326+ component, so a plain ``os.path.relpath`` escapes with ``..``. They may even
327+ differ in the ``dataset_<key>_files`` leaf itself: the compute side is
328+ uuid-keyed under outputs_to_working_directory while the store may be
329+ id-keyed — pass distinct ``store_leaf``/``compute_leaf`` to model that. The
330+ recorded absolute path deliberately does NOT exist under
331+ ``extra_files_path`` here, so the fix must be structural (no existence check).
332+ """
333+ index = "mpa_toy"
334+ compute_leaf = compute_leaf or "dataset_00000000-0000-0000-0000-000000000000_files"
335+ store_leaf = store_leaf or compute_leaf
336+
337+ # Object-store extra-files dir: where the bundle index actually gets written.
338+ extra_files_path = tmp_path / "objectstore" / "000" / store_leaf
339+ extra_files_path .mkdir (parents = True )
340+
341+ # Absolute path as recorded by the DM under the (now-irrelevant) job working dir.
342+ job_dir = tmp_path / "jobs" / "001" / "outputs" / compute_leaf
343+ recorded_path = str (job_dir / (index if recorded_relative_path is None else recorded_relative_path ))
344+
345+ output = {"data_tables" : {table_name : [{"value" : index , "name" : "toy" , path_column : recorded_path }]}}
346+ output_dataset_path = tmp_path / "output.dat"
347+ output_dataset_path .write_text (json .dumps (output ))
348+ output_dataset = OutputDataset (output_dataset_path , extra_files_path )
349+ out_data = {"out1" : output_dataset }
350+ data_table = {
351+ "name" : table_name ,
352+ "output" : {
353+ "columns" : [
354+ {"name" : "value" },
355+ {"name" : "name" },
356+ {
357+ "name" : path_column ,
358+ "output_ref" : "out1" ,
359+ "moves" : [
360+ {
361+ "type" : "directory" ,
362+ "relativize_symlinks" : False ,
363+ "source_value" : "${" + path_column + "}" ,
364+ "target_value" : "metaphlan/data/${value}" ,
365+ "target_base" : "${GALAXY_DATA_MANAGER_DATA_PATH}" ,
366+ }
367+ ],
368+ "value_translations" : [
369+ {"value" : "${GALAXY_DATA_MANAGER_DATA_PATH}/metaphlan/data/${value}" , "type" : "template" },
370+ {"value" : "abspath" , "type" : "function" },
371+ ],
372+ },
373+ ]
374+ },
375+ }
376+ process_description = DataTableBundleProcessorDescription (
377+ ** {"undeclared_tables" : False , "data_tables" : [data_table ]}
378+ )
379+ return index , extra_files_path , job_dir , out_data , process_description
380+
381+
382+ def test_write_bundle_relativizes_split_prefix_path_from_compute_root (tdt_manager , tmp_path ):
383+ """A data manager's compute-side path is made relative to its known job-dir root."""
384+ index , extra_files_path , compute_extra_files_path , out_data , process_description = (
385+ prepare_split_prefix_output_and_description (tmp_path )
386+ )
387+ tdt_manager .write_bundle (
388+ out_data ,
389+ process_description ,
390+ None ,
391+ source_extra_files_paths = {"out1" : str (compute_extra_files_path )},
392+ )
393+
394+ bundle_index = json .loads ((extra_files_path / BUNDLE_INDEX_FILE_NAME ).read_text ())
395+ stored_path = bundle_index ["data_tables" ]["testalpha" ][0 ]["path" ]
396+ assert stored_path == index
397+ assert not os .path .isabs (stored_path )
398+
399+
400+ def test_write_bundle_relativizes_uuid_keyed_job_path_on_id_keyed_store (tdt_manager , tmp_path ):
401+ """A uuid-keyed compute root works even when the object store is id-keyed."""
402+ index , extra_files_path , compute_extra_files_path , out_data , process_description = (
403+ prepare_split_prefix_output_and_description (
404+ tmp_path ,
405+ store_leaf = "dataset_42_files" ,
406+ compute_leaf = "dataset_00000000-0000-0000-0000-000000000000_files" ,
407+ )
408+ )
409+ tdt_manager .write_bundle (
410+ out_data ,
411+ process_description ,
412+ None ,
413+ source_extra_files_paths = {"out1" : str (compute_extra_files_path )},
414+ )
415+
416+ bundle_index = json .loads ((extra_files_path / BUNDLE_INDEX_FILE_NAME ).read_text ())
417+ stored_path = bundle_index ["data_tables" ]["testalpha" ][0 ]["path" ]
418+ assert stored_path == index
419+ assert not os .path .isabs (stored_path )
420+
421+
422+ def test_write_bundle_relativizes_compute_extra_files_root (tdt_manager , tmp_path ):
423+ _ , extra_files_path , compute_extra_files_path , out_data , process_description = (
424+ prepare_split_prefix_output_and_description (tmp_path , recorded_relative_path = "" )
425+ )
426+ tdt_manager .write_bundle (
427+ out_data ,
428+ process_description ,
429+ None ,
430+ source_extra_files_paths = {"out1" : str (compute_extra_files_path )},
431+ )
432+
433+ bundle_index = json .loads ((extra_files_path / BUNDLE_INDEX_FILE_NAME ).read_text ())
434+ assert bundle_index ["data_tables" ]["testalpha" ][0 ]["path" ] == os .curdir
435+
436+
437+ def test_write_bundle_preserves_nested_dataset_named_directory (tdt_manager , tmp_path ):
438+ store_leaf = "dataset_42_files"
439+ relative_path = os .path .join ("mpa_toy" , store_leaf , "index" )
440+ _ , extra_files_path , compute_extra_files_path , out_data , process_description = (
441+ prepare_split_prefix_output_and_description (
442+ tmp_path , store_leaf = store_leaf , recorded_relative_path = relative_path
443+ )
444+ )
445+ tdt_manager .write_bundle (
446+ out_data ,
447+ process_description ,
448+ None ,
449+ source_extra_files_paths = {"out1" : str (compute_extra_files_path )},
450+ )
451+
452+ bundle_index = json .loads ((extra_files_path / BUNDLE_INDEX_FILE_NAME ).read_text ())
453+ assert bundle_index ["data_tables" ]["testalpha" ][0 ]["path" ] == relative_path
454+
455+
312456def test_path_headers_from_move_and_abspath ():
313457 """A column is a path column when it has a move or an abspath translation, whatever its name."""
314458 description = DataTableBundleProcessorDescription (
0 commit comments