app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit cd707bd59c2874add5e27788b1ec954969cd4462
parent c4956a526a6bec1b4766ca3d8f6ba28284ec749f
Author: triesap <tyson@radroots.org>
Date:   Tue, 21 Apr 2026 21:08:03 +0000

app: prove pack day artifact action planning

Diffstat:
Mcrates/launchers/desktop/src/pack_day_host_handoff.rs | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mcrates/launchers/desktop/src/runtime.rs | 72+++++++++++++++++++++++++++++++++++++++++++++---------------------------
2 files changed, 152 insertions(+), 29 deletions(-)

diff --git a/crates/launchers/desktop/src/pack_day_host_handoff.rs b/crates/launchers/desktop/src/pack_day_host_handoff.rs @@ -366,6 +366,12 @@ mod tests { } } + fn write_artifact(bundle_directory: &PathBuf, file_name: &str) -> PathBuf { + let path = bundle_directory.join(file_name); + fs::write(&path, file_name).expect("artifact should write"); + path + } + #[test] fn reveal_bundle_plan_uses_open_reveal_for_the_bundle_directory() { let temp_dir = TestDirectory::new(); @@ -389,8 +395,7 @@ mod tests { #[test] fn open_pack_sheet_plan_targets_the_exported_pack_sheet() { let temp_dir = TestDirectory::new(); - let pack_sheet_path = temp_dir.path().join("pack_sheet.txt"); - fs::write(&pack_sheet_path, "pack day").expect("pack sheet should write"); + let pack_sheet_path = write_artifact(temp_dir.path(), "pack_sheet.txt"); let bundle = sample_bundle(temp_dir.path()); let plan = plan_pack_day_host_handoff(&bundle, PackDayHostHandoffKind::OpenPackSheet) @@ -406,6 +411,42 @@ mod tests { } #[test] + fn open_pickup_roster_plan_targets_the_exported_pickup_roster() { + let temp_dir = TestDirectory::new(); + let pickup_roster_path = write_artifact(temp_dir.path(), "pickup_roster.txt"); + let bundle = sample_bundle(temp_dir.path()); + + let plan = plan_pack_day_host_handoff(&bundle, PackDayHostHandoffKind::OpenPickupRoster) + .expect("open pickup roster plan should build"); + + assert_eq!(plan.kind, PackDayHostHandoffKind::OpenPickupRoster); + assert_eq!(plan.target_path, pickup_roster_path.clone()); + assert_eq!(plan.command_program, "open"); + assert_eq!( + plan.command_args, + vec![pickup_roster_path.to_string_lossy().into_owned()] + ); + } + + #[test] + fn open_customer_labels_plan_targets_the_exported_customer_labels() { + let temp_dir = TestDirectory::new(); + let customer_labels_path = write_artifact(temp_dir.path(), "customer_labels.txt"); + let bundle = sample_bundle(temp_dir.path()); + + let plan = plan_pack_day_host_handoff(&bundle, PackDayHostHandoffKind::OpenCustomerLabels) + .expect("open customer labels plan should build"); + + assert_eq!(plan.kind, PackDayHostHandoffKind::OpenCustomerLabels); + assert_eq!(plan.target_path, customer_labels_path.clone()); + assert_eq!(plan.command_program, "open"); + assert_eq!( + plan.command_args, + vec![customer_labels_path.to_string_lossy().into_owned()] + ); + } + + #[test] fn planning_fails_when_the_bundle_directory_is_missing() { let bundle_directory = std::env::temp_dir().join(format!( "radroots_app_pack_day_host_handoff_missing_{}", @@ -445,6 +486,26 @@ mod tests { } #[test] + fn planning_fails_when_pickup_roster_reference_is_missing() { + let temp_dir = TestDirectory::new(); + let mut bundle = sample_bundle(temp_dir.path()); + bundle + .artifacts + .retain(|artifact| artifact.kind != PackDayExportArtifactKind::PickupRoster); + + let error = plan_pack_day_host_handoff(&bundle, PackDayHostHandoffKind::OpenPickupRoster) + .expect_err("missing pickup roster artifact should fail"); + + assert_eq!( + error, + PackDayHostHandoffError::MissingArtifactReference { + kind: PackDayHostHandoffKind::OpenPickupRoster, + artifact_kind: PackDayExportArtifactKind::PickupRoster, + } + ); + } + + #[test] fn planning_fails_when_pack_sheet_relative_path_is_invalid() { let temp_dir = TestDirectory::new(); let mut bundle = sample_bundle(temp_dir.path()); @@ -463,6 +524,23 @@ mod tests { } #[test] + fn planning_fails_when_customer_labels_target_is_missing_on_disk() { + let temp_dir = TestDirectory::new(); + let bundle = sample_bundle(temp_dir.path()); + + let error = plan_pack_day_host_handoff(&bundle, PackDayHostHandoffKind::OpenCustomerLabels) + .expect_err("missing customer labels file should fail"); + + assert_eq!( + error, + PackDayHostHandoffError::MissingTargetPath { + kind: PackDayHostHandoffKind::OpenCustomerLabels, + path: temp_dir.path().join("customer_labels.txt"), + } + ); + } + + #[test] fn execution_classifies_command_launch_failures() { let temp_dir = TestDirectory::new(); let bundle = sample_bundle(temp_dir.path()); @@ -510,6 +588,33 @@ mod tests { } #[test] + fn execution_classifies_nonzero_exit_failures_for_customer_labels() { + let temp_dir = TestDirectory::new(); + write_artifact(temp_dir.path(), "customer_labels.txt"); + let bundle = sample_bundle(temp_dir.path()); + let plan = plan_pack_day_host_handoff(&bundle, PackDayHostHandoffKind::OpenCustomerLabels) + .expect("customer labels plan should build"); + + let error = execute_pack_day_host_handoff_plan_with(&plan, |_| { + Ok(PackDayHostHandoffCommandResult::failed( + Some(1), + "labels unavailable", + )) + }) + .expect_err("nonzero exit should classify"); + + assert_eq!( + error, + PackDayHostHandoffError::CommandFailed { + kind: PackDayHostHandoffKind::OpenCustomerLabels, + program: "open".to_owned(), + exit_code: Some(1), + stderr: "labels unavailable".to_owned(), + } + ); + } + + #[test] fn execution_accepts_successful_runs() { let temp_dir = TestDirectory::new(); let bundle = sample_bundle(temp_dir.path()); diff --git a/crates/launchers/desktop/src/runtime.rs b/crates/launchers/desktop/src/runtime.rs @@ -7247,7 +7247,7 @@ mod tests { } #[test] - fn runtime_prepare_pack_day_host_handoff_uses_the_current_export_bundle() { + fn runtime_prepare_pack_day_host_handoff_uses_the_current_export_bundle_for_file_actions() { let (runtime, paths) = bootstrapped_runtime("pack_day_host_handoff_prepare"); let (_, farm_id) = provision_ready_farmer_account(&runtime); @@ -7259,33 +7259,51 @@ mod tests { .expect("pack day export should succeed") ); - let prepared = runtime - .prepare_pack_day_host_handoff(PackDayHostHandoffKind::OpenPackSheet) - .expect("host handoff should prepare") - .expect("host handoff should produce a plan"); + for (kind, suffix) in [ + (PackDayHostHandoffKind::OpenPackSheet, "pack_sheet.txt"), + ( + PackDayHostHandoffKind::OpenPickupRoster, + "pickup_roster.txt", + ), + ( + PackDayHostHandoffKind::OpenCustomerLabels, + "customer_labels.txt", + ), + ] { + let prepared = runtime + .prepare_pack_day_host_handoff(kind) + .expect("host handoff should prepare") + .expect("host handoff should produce a plan"); + + let summary = runtime.summary(); + assert_eq!( + summary.pack_day_projection.host_handoff.status, + PackDayHostHandoffStatus::Running + ); + assert_eq!( + summary.pack_day_projection.host_handoff.request, + Some(prepared.0.clone()) + ); + assert_eq!(prepared.0.kind, kind); + assert_eq!( + prepared.0.bundle_directory, + summary + .pack_day_projection + .export + .bundle + .as_ref() + .expect("pack day export bundle") + .bundle_directory + ); + assert_eq!(prepared.1.kind, kind); + assert!(prepared.1.target_path.ends_with(suffix)); - let summary = runtime.summary(); - assert_eq!( - summary.pack_day_projection.host_handoff.status, - PackDayHostHandoffStatus::Running - ); - assert_eq!( - summary.pack_day_projection.host_handoff.request, - Some(prepared.0.clone()) - ); - assert_eq!(prepared.0.kind, PackDayHostHandoffKind::OpenPackSheet); - assert_eq!( - prepared.0.bundle_directory, - summary - .pack_day_projection - .export - .bundle - .as_ref() - .expect("pack day export bundle") - .bundle_directory - ); - assert_eq!(prepared.1.kind, PackDayHostHandoffKind::OpenPackSheet); - assert!(prepared.1.target_path.ends_with("pack_sheet.txt")); + assert!( + runtime + .finish_pack_day_host_handoff(prepared.0, Ok(())) + .expect("host handoff success should apply") + ); + } cleanup_bootstrapped_runtime_paths(&paths); }