lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

order_work.rs (10901B)


      1 use radroots_local_events::{
      2     BUYER_ORDER_REQUEST_ACTOR_SOURCE_RESOLVED_ACCOUNT,
      3     BUYER_ORDER_REQUEST_ACTOR_SOURCE_UNRESOLVED_APP, BUYER_ORDER_REQUEST_DOCUMENT_KIND,
      4     BUYER_ORDER_REQUEST_LOCAL_WORK_RECORD_KIND, BuyerOrderRequestSupportState,
      5     buyer_order_request_local_work_record_id, validate_buyer_order_request_local_work_payload,
      6     validate_supported_buyer_order_request_local_work_payload,
      7     validate_unsupported_buyer_order_request_local_work_payload,
      8 };
      9 use serde_json::{Value, json};
     10 
     11 #[test]
     12 fn buyer_order_request_record_id_is_deterministic_for_app_orders() {
     13     assert_eq!(
     14         buyer_order_request_local_work_record_id(" order-1 ").expect("record id"),
     15         "app:local_work:order_request:order-1"
     16     );
     17 }
     18 
     19 #[test]
     20 fn buyer_order_request_payload_accepts_supported_exportable_work() {
     21     let payload = supported_payload();
     22 
     23     let validation =
     24         validate_buyer_order_request_local_work_payload(&payload).expect("valid payload");
     25     let supported = validate_supported_buyer_order_request_local_work_payload(&payload)
     26         .expect("supported payload");
     27 
     28     assert_eq!(validation.order_id, "ord_1");
     29     assert_eq!(
     30         validation.support_state,
     31         BuyerOrderRequestSupportState::Supported
     32     );
     33     assert_eq!(validation.support_state.as_str(), "supported");
     34     assert!(validation.support_issues.is_empty());
     35     assert_eq!(supported, validation);
     36 }
     37 
     38 #[test]
     39 fn buyer_order_request_payload_accepts_explicit_unsupported_work() {
     40     let mut payload = supported_payload();
     41     payload["exportability"] = json!({
     42         "state": "identity_unresolved",
     43         "reason": "canonical_hex_pubkey_required"
     44     });
     45     payload["support_status"] = json!({
     46         "state": "unsupported",
     47         "issues": ["buyer_pubkey_required"]
     48     });
     49     payload["document"]["order"]["buyer_pubkey"] = json!("");
     50     payload["document"]["buyer_actor"]["pubkey"] = json!("");
     51     payload["document"]["buyer_actor"]["source"] =
     52         json!(BUYER_ORDER_REQUEST_ACTOR_SOURCE_UNRESOLVED_APP);
     53 
     54     let validation =
     55         validate_buyer_order_request_local_work_payload(&payload).expect("valid payload");
     56     let unsupported = validate_unsupported_buyer_order_request_local_work_payload(&payload)
     57         .expect("unsupported payload");
     58     let supported_error = validate_supported_buyer_order_request_local_work_payload(&payload)
     59         .expect_err("unsupported payload should not validate as supported");
     60 
     61     assert_eq!(
     62         validation.support_state,
     63         BuyerOrderRequestSupportState::Unsupported
     64     );
     65     assert_eq!(validation.support_state.as_str(), "unsupported");
     66     assert_eq!(validation.support_issues, vec!["buyer_pubkey_required"]);
     67     assert_eq!(unsupported, validation);
     68     assert!(supported_error.to_string().contains("support_status.state"));
     69 }
     70 
     71 #[test]
     72 fn buyer_order_request_payload_rejects_missing_identity() {
     73     for (path, expected) in [
     74         (vec!["document", "order", "listing_addr"], "listing_addr"),
     75         (
     76             vec!["document", "order", "listing_event_id"],
     77             "listing_event_id",
     78         ),
     79         (vec!["document", "order", "seller_pubkey"], "seller_pubkey"),
     80         (vec!["document", "order", "buyer_pubkey"], "buyer_pubkey"),
     81     ] {
     82         let mut payload = supported_payload();
     83         set_path(&mut payload, &path, json!(""));
     84 
     85         assert_invalid(payload, expected);
     86     }
     87 }
     88 
     89 #[test]
     90 fn buyer_order_request_payload_rejects_missing_items() {
     91     let mut payload = supported_payload();
     92     payload["document"]["order"]["items"] = json!([]);
     93 
     94     assert_invalid(payload, "items");
     95 }
     96 
     97 #[test]
     98 fn buyer_order_request_payload_rejects_invalid_item_identity() {
     99     let mut missing_bin = supported_payload();
    100     missing_bin["document"]["order"]["items"][0]["bin_id"] = json!("");
    101     assert_invalid(missing_bin, "items[0].bin_id");
    102 
    103     let mut zero_count = supported_payload();
    104     zero_count["document"]["order"]["items"][0]["bin_count"] = json!(0);
    105     assert_invalid(zero_count, "items[0].bin_count");
    106 }
    107 
    108 #[test]
    109 fn buyer_order_request_payload_rejects_invalid_economics() {
    110     let mut missing_economics = supported_payload();
    111     missing_economics["document"]["order"]
    112         .as_object_mut()
    113         .expect("order object")
    114         .remove("economics");
    115     assert_invalid(missing_economics, "economics");
    116 
    117     let mut non_object_economics = supported_payload();
    118     non_object_economics["document"]["order"]["economics"] = Value::Null;
    119     assert_invalid(non_object_economics, "economics");
    120 
    121     let mut mismatched_currency = supported_payload();
    122     mismatched_currency["document"]["order"]["economics"]["items"][0]["unit_price_currency"] =
    123         json!("CAD");
    124     assert_invalid(mismatched_currency, "unit_price_currency");
    125 
    126     let mut mismatched_items = supported_payload();
    127     mismatched_items["document"]["order"]["economics"]["items"] = json!([
    128         {
    129             "bin_id": "dozen-eggs",
    130             "bin_count": 2,
    131             "quantity_amount": "1",
    132             "quantity_unit": "dozen",
    133             "unit_price_amount": "8.00",
    134             "unit_price_currency": "USD",
    135             "line_subtotal": {
    136                 "amount": "16.00",
    137                 "currency": "USD"
    138             }
    139         },
    140         {
    141             "bin_id": "half-dozen-eggs",
    142             "bin_count": 1
    143         }
    144     ]);
    145     assert_invalid(mismatched_items, "economics.items");
    146 
    147     let mut empty_economics_items = supported_payload();
    148     empty_economics_items["document"]["order"]["economics"]["items"] = json!([]);
    149     assert_invalid(empty_economics_items, "economics.items");
    150 
    151     let mut mismatched_bin = supported_payload();
    152     mismatched_bin["document"]["order"]["economics"]["items"][0]["bin_id"] = json!("other-bin");
    153     assert_invalid(mismatched_bin, "economics.items[0].bin_id");
    154 
    155     let mut bad_currency_length = supported_payload();
    156     bad_currency_length["document"]["order"]["economics"]["currency"] = json!("US");
    157     assert_invalid(bad_currency_length, "currency");
    158 
    159     let mut missing_line_subtotal = supported_payload();
    160     missing_line_subtotal["document"]["order"]["economics"]["items"][0]
    161         .as_object_mut()
    162         .expect("economics item")
    163         .remove("line_subtotal");
    164     assert_invalid(missing_line_subtotal, "line_subtotal");
    165 }
    166 
    167 #[test]
    168 fn buyer_order_request_payload_rejects_stale_or_conflicting_currentness() {
    169     let mut stale = supported_payload();
    170     stale["currentness"]["current"] = json!(false);
    171     assert_invalid(stale, "currentness.current");
    172 
    173     let mut missing_current = supported_payload();
    174     missing_current["currentness"]["current"] = Value::Null;
    175     assert_invalid(missing_current, "currentness.current");
    176 
    177     let mut wrong_order = supported_payload();
    178     wrong_order["currentness"]["order_id"] = json!("ord_other");
    179     assert_invalid(wrong_order, "currentness.order_id");
    180 }
    181 
    182 #[test]
    183 fn buyer_order_request_payload_rejects_malformed_support_status() {
    184     let mut supported_with_issue = supported_payload();
    185     supported_with_issue["support_status"]["issues"] = json!(["unit_price_required"]);
    186     assert_invalid(supported_with_issue, "support_status.issues");
    187 
    188     let mut unsupported_without_issue = supported_payload();
    189     unsupported_without_issue["support_status"] = json!({
    190         "state": "unsupported",
    191         "issues": []
    192     });
    193     assert_invalid(unsupported_without_issue, "support_status.issues");
    194 }
    195 
    196 fn supported_payload() -> Value {
    197     json!({
    198         "record_kind": BUYER_ORDER_REQUEST_LOCAL_WORK_RECORD_KIND,
    199         "scope": "app",
    200         "exportability": {
    201             "state": "exportable"
    202         },
    203         "support_status": {
    204             "state": "supported",
    205             "issues": []
    206         },
    207         "currentness": {
    208             "current": true,
    209             "source": "app_sqlite_order",
    210             "record_id": "app:local_work:order_request:ord_1",
    211             "order_id": "ord_1",
    212             "order_updated_at": "2026-05-24T12:00:00Z",
    213             "created_at_ms": 1777777777000_i64
    214         },
    215         "document": {
    216             "version": 1,
    217             "kind": BUYER_ORDER_REQUEST_DOCUMENT_KIND,
    218             "order": {
    219                 "order_id": "ord_1",
    220                 "listing_addr": "30402:seller_pubkey:listing_key",
    221                 "listing_event_id": "event-listing-1",
    222                 "buyer_pubkey": "buyer_pubkey",
    223                 "seller_pubkey": "seller_pubkey",
    224                 "items": [
    225                     {
    226                         "bin_id": "dozen-eggs",
    227                         "bin_count": 2
    228                     }
    229                 ],
    230                 "economics": {
    231                     "quote_id": "app-order:ord_1",
    232                     "quote_version": 1,
    233                     "pricing_basis": "listing_event",
    234                     "currency": "USD",
    235                     "items": [
    236                         {
    237                             "bin_id": "dozen-eggs",
    238                             "bin_count": 2,
    239                             "quantity_amount": "1",
    240                             "quantity_unit": "dozen",
    241                             "unit_price_amount": "8.00",
    242                             "unit_price_currency": "USD",
    243                             "line_subtotal": {
    244                                 "amount": "16.00",
    245                                 "currency": "USD"
    246                             }
    247                         }
    248                     ],
    249                     "discounts": [],
    250                     "adjustments": [],
    251                     "subtotal": {
    252                         "amount": "16.00",
    253                         "currency": "USD"
    254                     },
    255                     "discount_total": {
    256                         "amount": "0",
    257                         "currency": "USD"
    258                     },
    259                     "adjustment_total": {
    260                         "amount": "0",
    261                         "currency": "USD"
    262                     },
    263                     "total": {
    264                         "amount": "16.00",
    265                         "currency": "USD"
    266                     }
    267                 }
    268             },
    269             "buyer_actor": {
    270                 "account_id": "buyer-account",
    271                 "pubkey": "buyer_pubkey",
    272                 "source": BUYER_ORDER_REQUEST_ACTOR_SOURCE_RESOLVED_ACCOUNT
    273             },
    274             "listing_lookup": "30402:seller_pubkey:listing_key"
    275         }
    276     })
    277 }
    278 
    279 fn assert_invalid(payload: Value, expected: &str) {
    280     let error =
    281         validate_buyer_order_request_local_work_payload(&payload).expect_err("invalid payload");
    282     assert!(
    283         error.to_string().contains(expected),
    284         "expected error to contain {expected}, got {error}"
    285     );
    286 }
    287 
    288 fn set_path(payload: &mut Value, path: &[&str], value: Value) {
    289     let mut current = payload;
    290     for segment in &path[..path.len() - 1] {
    291         current = current.get_mut(*segment).expect("path segment");
    292     }
    293     current[path[path.len() - 1]] = value;
    294 }