use std::time::Duration; use fabro_test::{fabro_snapshot, test_context}; use crate::cmd::support::{read_text, setup_seeded_artifact_run, text_tree}; fn artifact_filters(context: &fabro_test::TestContext) -> Vec<(String, String)> { let mut filters = context.filters(); filters.push(( r"\[STORAGE_DIR\]/scratch/\d{8}-\[ULID\]".to_string(), "[RUN_DIR]".to_string(), )); filters } #[test] fn artifact_commands_share_populated_run_fixture() { let context = test_context!(); let run = setup_seeded_artifact_run(&context); let filters = artifact_filters(&context); let mut list_json = context.command(); list_json.args(["artifact", "list", &run.run_id, "--json"]); fabro_snapshot!(filters.clone(), list_json, @r#" success: true exit_code: 0 ----- stdout ----- [ { "node_slug": "create_assets", "retry": 1, "relative_path": "assets/node_a/summary.txt", "size": 5 }, { "node_slug": "create_assets", "retry": 1, "relative_path": "assets/shared/report.txt", "size": 3 }, { "node_slug": "create_colliding", "retry": 1, "relative_path": "assets/other/summary.txt", "size": 4 }, { "node_slug": "create_colliding", "retry": 1, "relative_path": "assets/retry/report.txt", "size": 6 }, { "node_slug": "retry_assets", "retry": 1, "relative_path": "assets/retry/report.txt", "size": 5 }, { "node_slug": "retry_assets", "retry": 2, "relative_path": "assets/retry/report.txt", "size": 6 } ] ----- stderr ----- "#); let mut list_filtered = context.command(); list_filtered.args([ "artifact", "list", &run.run_id, "--node", "retry_assets", "--retry", "2", "--json", ]); fabro_snapshot!(filters.clone(), list_filtered, @r#" success: true exit_code: 0 ----- stdout ----- [ { "node_slug": "retry_assets", "retry": 2, "relative_path": "assets/retry/report.txt", "size": 6 } ] ----- stderr ----- "#); let single_dest = context.temp_dir.join("artifact-one"); let mut cp_single = context.command(); cp_single.args([ "artifact", "cp", &format!("{}:assets/shared/report.txt", run.run_id), single_dest.to_str().unwrap(), "--node", "create_assets", ]); fabro_snapshot!(context.filters(), cp_single, @" success: true exit_code: 0 ----- stdout ----- Copied assets/shared/report.txt to [TEMP_DIR]/artifact-one/report.txt ----- stderr ----- "); assert_eq!(read_text(&single_dest.join("report.txt")), "one"); let tree_dest = context.temp_dir.join("artifact-tree"); let mut cp_tree = context.command(); cp_tree.args([ "artifact", "cp", &run.run_id, tree_dest.to_str().unwrap(), "--tree", ]); cp_tree.timeout(Duration::from_secs(30)); fabro_snapshot!(context.filters(), cp_tree, @" success: true exit_code: 0 ----- stdout ----- Copied 6 artifact(s) to [TEMP_DIR]/artifact-tree ----- stderr ----- "); insta::assert_snapshot!( text_tree(&tree_dest).join("\n"), @r" create_assets/retry_1/assets/node_a/summary.txt = alpha create_assets/retry_1/assets/shared/report.txt = one create_colliding/retry_1/assets/other/summary.txt = beta create_colliding/retry_1/assets/retry/report.txt = second retry_assets/retry_1/assets/retry/report.txt = first retry_assets/retry_2/assets/retry/report.txt = second " ); let ambiguous_dest = context.temp_dir.join("artifact-ambiguous"); let mut cp_ambiguous = context.command(); cp_ambiguous.args([ "artifact", "cp", &format!("{}:assets/retry/report.txt", run.run_id), ambiguous_dest.to_str().unwrap(), ]); fabro_snapshot!(context.filters(), cp_ambiguous, @" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- × Path 'assets/retry/report.txt' matches multiple artifacts: create_colliding:retry_1, retry_assets:retry_1, retry_assets:retry_2. Use --node and/or --retry to disambiguate. "); let flat_dest = context.temp_dir.join("artifact-flat"); let mut cp_flat = context.command(); cp_flat.args(["artifact", "cp", &run.run_id, flat_dest.to_str().unwrap()]); fabro_snapshot!(context.filters(), cp_flat, @" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- × Filename collision: 'summary.txt' exists in both create_assets:retry_1 and create_colliding:retry_1. Use --tree to preserve directory structure, or --node and/or --retry to filter. "); }