app

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

commit c97b8974ed50e1759a9594a0913f1888845d3c58
parent c4bd463cef9a40f2f284250ccc4a72099f4c493a
Author: triesap <tyson@radroots.org>
Date:   Wed,  3 Jun 2026 18:30:05 -0700

tests: refresh workflow surface coverage

Diffstat:
Mcrates/desktop/src/window.rs | 157++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcrates/store/src/repo/buyer.rs | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 248 insertions(+), 1 deletion(-)

diff --git a/crates/desktop/src/window.rs b/crates/desktop/src/window.rs @@ -13368,6 +13368,8 @@ mod tests { startup_issue_summary_text, startup_notice_text, startup_signer_preview_summary, startup_signer_preview_summary_for_connect_state, startup_signer_source_input_is_editable, startup_signer_status_spec, startup_signer_transport_failure_requires_notice, + trade_agreement_status_key, trade_fulfillment_status_key, trade_inventory_status_key, + trade_payment_display_status_key, trade_revision_status_key, trade_workflow_source_key, }; use crate::runtime::{ DesktopAppRuntimeMetadataSummary, DesktopAppRuntimeSummary, DesktopAppSyncConflictSummary, @@ -13404,7 +13406,9 @@ mod tests { ReminderDeadlineProjection, ReminderDeliveryState, ReminderId, ReminderKind, ReminderSurface, ReminderUrgency, RepeatDemandEligibility, RepeatDemandHandoffProjection, ShellSection, TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind, - TradeEconomicsProjection, TradePaymentDisplayStatus, TradeWorkflowProjection, + TradeAgreementStatus, TradeEconomicsProjection, TradeFulfillmentStatus, + TradeInventoryStatus, TradePaymentDisplayStatus, TradeRevisionStatus, + TradeWorkflowProjection, TradeWorkflowSource, }; use radroots_identity::RadrootsIdentity; use std::{ @@ -13703,6 +13707,157 @@ mod tests { } #[test] + fn trade_workflow_badge_keys_cover_refactored_status_axes() { + for (status, key) in [ + ( + TradeAgreementStatus::Ordered, + AppTextKey::TradeWorkflowAgreementOrdered, + ), + ( + TradeAgreementStatus::Confirmed, + AppTextKey::TradeWorkflowAgreementConfirmed, + ), + ( + TradeAgreementStatus::Declined, + AppTextKey::TradeWorkflowAgreementDeclined, + ), + ( + TradeAgreementStatus::Cancelled, + AppTextKey::TradeWorkflowAgreementCancelled, + ), + ( + TradeAgreementStatus::Completed, + AppTextKey::TradeWorkflowAgreementCompleted, + ), + ( + TradeAgreementStatus::NeedsReview, + AppTextKey::TradeWorkflowAgreementNeedsReview, + ), + ] { + assert_eq!(trade_agreement_status_key(status), key); + assert!(!app_text(key).is_empty()); + } + + for (status, key) in [ + ( + TradeRevisionStatus::None, + AppTextKey::TradeWorkflowRevisionNone, + ), + ( + TradeRevisionStatus::ChangeProposed, + AppTextKey::TradeWorkflowRevisionChangeProposed, + ), + ( + TradeRevisionStatus::Updated, + AppTextKey::TradeWorkflowRevisionUpdated, + ), + ( + TradeRevisionStatus::KeptAsPlaced, + AppTextKey::TradeWorkflowRevisionKeptAsPlaced, + ), + ] { + assert_eq!(trade_revision_status_key(status), key); + assert!(!app_text(key).is_empty()); + } + + for (status, key) in [ + ( + TradeFulfillmentStatus::Confirmed, + AppTextKey::TradeWorkflowFulfillmentConfirmed, + ), + ( + TradeFulfillmentStatus::Preparing, + AppTextKey::TradeWorkflowFulfillmentPreparing, + ), + ( + TradeFulfillmentStatus::ReadyForPickup, + AppTextKey::TradeWorkflowFulfillmentReadyForPickup, + ), + ( + TradeFulfillmentStatus::OutForDelivery, + AppTextKey::TradeWorkflowFulfillmentOutForDelivery, + ), + ( + TradeFulfillmentStatus::Delivered, + AppTextKey::TradeWorkflowFulfillmentDelivered, + ), + ( + TradeFulfillmentStatus::Cancelled, + AppTextKey::TradeWorkflowFulfillmentCancelled, + ), + ] { + assert_eq!(trade_fulfillment_status_key(status), key); + assert!(!app_text(key).is_empty()); + } + + for (status, key) in [ + ( + TradeInventoryStatus::Available, + AppTextKey::TradeWorkflowInventoryAvailable, + ), + ( + TradeInventoryStatus::Reserved, + AppTextKey::TradeWorkflowInventoryReserved, + ), + ( + TradeInventoryStatus::SoldOut, + AppTextKey::TradeWorkflowInventorySoldOut, + ), + ( + TradeInventoryStatus::NeedsReview, + AppTextKey::TradeWorkflowInventoryNeedsReview, + ), + ] { + assert_eq!(trade_inventory_status_key(status), key); + assert!(!app_text(key).is_empty()); + } + + for (status, key) in [ + ( + TradePaymentDisplayStatus::NotRecorded, + AppTextKey::TradeWorkflowPaymentNotRecorded, + ), + ( + TradePaymentDisplayStatus::Recorded, + AppTextKey::TradeWorkflowPaymentRecorded, + ), + ( + TradePaymentDisplayStatus::NeedsReview, + AppTextKey::TradeWorkflowPaymentNeedsReview, + ), + ] { + assert_eq!(trade_payment_display_status_key(status), key); + assert!(!app_text(key).is_empty()); + } + + for (source, key) in [ + ( + TradeWorkflowSource::App, + AppTextKey::TradeWorkflowProvenanceApp, + ), + ( + TradeWorkflowSource::Cli, + AppTextKey::TradeWorkflowProvenanceCli, + ), + ( + TradeWorkflowSource::Relay, + AppTextKey::TradeWorkflowProvenanceRelay, + ), + ( + TradeWorkflowSource::LocalEvents, + AppTextKey::TradeWorkflowProvenanceLocalEvents, + ), + ( + TradeWorkflowSource::Unknown, + AppTextKey::TradeWorkflowProvenanceUnknown, + ), + ] { + assert_eq!(trade_workflow_source_key(source), key); + assert!(!app_text(key).is_empty()); + } + } + + #[test] fn today_route_has_no_setup_onboarding_card() { assert!(farm_setup_onboarding_card_spec(HomeRoute::Today).is_none()); } diff --git a/crates/store/src/repo/buyer.rs b/crates/store/src/repo/buyer.rs @@ -3558,6 +3558,71 @@ mod tests { } #[test] + fn buyer_order_projections_fail_closed_for_invalid_workflow_snapshot_keys() { + let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open"); + let connection = store.connection(); + let repository = AppBuyerRepository::new(connection); + let context = BuyerContext::account("acct_buyer"); + let farm_id = insert_farm(connection, "Willow Farm", "ready"); + let order_id = OrderId::new(); + + insert_order( + connection, + order_id, + farm_id, + "R-100", + "scheduled", + Some("account:acct_buyer"), + "buyer@example.com", + "", + "", + ); + set_order_workflow_display_projection( + connection, + order_id, + "confirmed", + Some("ready_for_pickup"), + "reserved", + "recorded", + "local_events", + Some("buyer-workflow-event"), + ); + + for (column, expected_field) in [ + ("workflow_agreement", "orders.workflow_agreement"), + ("workflow_fulfillment", "orders.workflow_fulfillment"), + ("workflow_inventory", "orders.workflow_inventory"), + ("workflow_payment", "orders.workflow_payment"), + ( + "workflow_provenance_source", + "orders.workflow_provenance_source", + ), + ] { + set_order_workflow_display_projection( + connection, + order_id, + "confirmed", + Some("ready_for_pickup"), + "reserved", + "recorded", + "local_events", + Some("buyer-workflow-event"), + ); + corrupt_order_workflow_display_projection(connection, order_id, column, "future_state"); + + let list_error = repository + .load_buyer_orders(&context) + .expect_err("invalid workflow snapshot should fail buyer list projection"); + let detail_error = repository + .load_buyer_order_detail(&context, order_id) + .expect_err("invalid workflow snapshot should fail buyer detail projection"); + + assert_decode_enum(list_error, expected_field, "future_state"); + assert_decode_enum(detail_error, expected_field, "future_state"); + } + } + + #[test] fn buyer_cart_rejects_cross_farm_lines() { let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open"); let farm_id = FarmId::new(); @@ -3907,6 +3972,33 @@ mod tests { .expect("check constraints should re-enable"); } + fn corrupt_order_workflow_display_projection( + connection: &Connection, + order_id: OrderId, + column: &str, + value: &str, + ) { + connection + .execute_batch("pragma ignore_check_constraints = on") + .expect("check constraints should disable"); + let statement = match column { + "workflow_agreement" => "update orders set workflow_agreement = ?1 where id = ?2", + "workflow_fulfillment" => "update orders set workflow_fulfillment = ?1 where id = ?2", + "workflow_inventory" => "update orders set workflow_inventory = ?1 where id = ?2", + "workflow_payment" => "update orders set workflow_payment = ?1 where id = ?2", + "workflow_provenance_source" => { + "update orders set workflow_provenance_source = ?1 where id = ?2" + } + _ => panic!("unsupported workflow display projection column {column}"), + }; + connection + .execute(statement, params![value, order_id.to_string()]) + .expect("order workflow display projection corruption should succeed"); + connection + .execute_batch("pragma ignore_check_constraints = off") + .expect("check constraints should re-enable"); + } + fn assert_decode_enum(error: AppSqliteError, expected_field: &str, expected_value: &str) { match error { AppSqliteError::DecodeEnum { field, value } => {