structured_encode_default.rs (35489B)
1 #![cfg(feature = "json")] 2 3 #[path = "../src/test_fixtures.rs"] 4 mod test_fixtures; 5 6 use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; 7 use radroots_event::envelope::kind::{ 8 KIND_COOP, KIND_DOCUMENT, KIND_FARM, KIND_PLOT, KIND_RESOURCE_AREA, KIND_RESOURCE_HARVEST_CAP, 9 }; 10 use radroots_event::farm::change_set::{GcsLocation, GeoJsonPoint, GeoJsonPolygon}; 11 use radroots_event::farm::coop::{Coop, CoopLocation, CoopRef}; 12 use radroots_event::farm::plot::{Plot, PlotLocation, PlotRef}; 13 use radroots_event::farm::resource_area::{ResourceArea, ResourceAreaLocation, ResourceAreaRef}; 14 use radroots_event::farm::resource_cap::{ResourceHarvestCap, ResourceHarvestProduct}; 15 use radroots_event::farm::{Farm, FarmPublicLocation, FarmRef}; 16 use radroots_event::id::{DTag, InventoryBinId}; 17 use radroots_event::listing::operational::{ 18 OperationalListing, OperationalListingBin, OperationalListingProduct, 19 }; 20 use radroots_event::post::document::{Document, DocumentSubject}; 21 use radroots_event::social::list_set::ListSet; 22 use radroots_event_codec::encode::EventEncodeError; 23 use radroots_event_codec::encode::coop::{ 24 coop_admins_list_set, coop_items_list_set, coop_members_farms_list_set, coop_members_list_set, 25 coop_owners_list_set, member_of_coops_list_set, 26 }; 27 use radroots_event_codec::encode::coop::{ 28 coop_build_tags, coop_ref_tags, to_wire_parts as coop_to_wire_parts, 29 to_wire_parts_with_kind as coop_to_wire_parts_with_kind, 30 }; 31 use radroots_event_codec::encode::document::{ 32 document_build_tags, to_wire_parts as document_to_wire_parts, 33 to_wire_parts_with_kind as document_to_wire_parts_with_kind, 34 }; 35 use radroots_event_codec::encode::farm::{ 36 farm_build_tags, farm_ref_tags, to_wire_parts as farm_to_wire_parts, 37 to_wire_parts_with_kind as farm_to_wire_parts_with_kind, 38 }; 39 use radroots_event_codec::encode::farm::{ 40 farm_members_list_set, farm_operational_listings_list_set, 41 farm_operational_listings_list_set_from_listings, farm_owners_list_set, farm_plots_list_set, 42 farm_plots_list_set_from_plots, farm_workers_list_set, member_of_farms_list_set, 43 }; 44 use radroots_event_codec::encode::plot::{ 45 plot_address, plot_build_tags, to_wire_parts as plot_to_wire_parts, 46 to_wire_parts_with_kind as plot_to_wire_parts_with_kind, 47 }; 48 use radroots_event_codec::encode::resource_area::{ 49 resource_area_build_tags, resource_area_ref_tags, to_wire_parts as resource_area_to_wire_parts, 50 to_wire_parts_with_kind as resource_area_to_wire_parts_with_kind, 51 }; 52 use radroots_event_codec::encode::resource_area::{ 53 resource_area_members_farms_list_set, resource_area_members_plots_list_set, 54 resource_area_stewards_list_set, 55 }; 56 use radroots_event_codec::encode::resource_cap::{ 57 resource_harvest_cap_build_tags, to_wire_parts as resource_cap_to_wire_parts, 58 to_wire_parts_with_kind as resource_cap_to_wire_parts_with_kind, 59 }; 60 use test_fixtures::FIXTURE_ALICE_PUBLIC_KEY_HEX; 61 62 const TEST_PUBKEY_HEX: &str = FIXTURE_ALICE_PUBLIC_KEY_HEX; 63 64 fn listing_d_tag(raw: &str) -> DTag { 65 raw.parse().unwrap() 66 } 67 68 fn bin_id(raw: &str) -> InventoryBinId { 69 raw.parse().unwrap() 70 } 71 72 fn sample_gcs() -> GcsLocation { 73 GcsLocation { 74 lat: 37.0, 75 lng: -122.0, 76 geohash: "9q8yy".to_string(), 77 point: GeoJsonPoint { 78 r#type: "Point".to_string(), 79 coordinates: [-122.0, 37.0], 80 }, 81 polygon: GeoJsonPolygon { 82 r#type: "Polygon".to_string(), 83 coordinates: vec![vec![ 84 [-122.0, 37.0], 85 [-122.0, 37.0001], 86 [-122.0001, 37.0001], 87 [-122.0, 37.0], 88 ]], 89 }, 90 accuracy: None, 91 altitude: None, 92 tag_0: None, 93 label: None, 94 area: None, 95 elevation: None, 96 soil: None, 97 climate: None, 98 gc_id: None, 99 gc_name: None, 100 gc_admin1_id: None, 101 gc_admin1_name: None, 102 gc_country_id: None, 103 gc_country_name: None, 104 } 105 } 106 107 fn sample_listing(d_tag: &str) -> OperationalListing { 108 let quantity = Quantity::try_new(Decimal::from(1u32), Unit::Each).unwrap(); 109 let price = QuantityPrice::try_new( 110 Money::try_new(Decimal::from(10u32), Currency::USD).unwrap(), 111 quantity.clone(), 112 ) 113 .unwrap(); 114 OperationalListing { 115 d_tag: listing_d_tag(d_tag), 116 published_at: None, 117 farm: FarmRef { 118 pubkey: TEST_PUBKEY_HEX.to_string(), 119 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 120 }, 121 product: OperationalListingProduct { 122 key: "sku".to_string(), 123 title: "Widget".to_string(), 124 category: "Tools".to_string(), 125 summary: None, 126 process: None, 127 lot: None, 128 location: None, 129 profile: None, 130 year: None, 131 }, 132 primary_bin_id: bin_id("bin-1"), 133 bins: vec![OperationalListingBin { 134 bin_id: bin_id("bin-1"), 135 quantity, 136 price_per_canonical_unit: price, 137 display_amount: None, 138 display_unit: None, 139 display_label: None, 140 display_price: None, 141 display_price_unit: None, 142 }], 143 resource_area: None, 144 plot: None, 145 discounts: None, 146 inventory_available: None, 147 availability: None, 148 delivery_method: None, 149 location: None, 150 images: None, 151 } 152 } 153 154 fn sample_farm() -> Farm { 155 Farm { 156 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 157 name: "Farm".to_string(), 158 about: None, 159 website: None, 160 picture: None, 161 banner: None, 162 location: Some(FarmPublicLocation { 163 primary: "farm".to_string(), 164 city: Some("Town".to_string()), 165 region: Some("Region".to_string()), 166 country: None, 167 geohash: "9q8yy".to_string(), 168 }), 169 tags: Some(vec!["organic".to_string(), " ".to_string()]), 170 } 171 } 172 173 fn sample_coop() -> Coop { 174 Coop { 175 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 176 name: "Coop".to_string(), 177 about: None, 178 website: None, 179 picture: None, 180 banner: None, 181 location: Some(CoopLocation { 182 primary: Some("coop".to_string()), 183 city: None, 184 region: None, 185 country: None, 186 gcs: sample_gcs(), 187 }), 188 tags: Some(vec!["co-op".to_string(), " ".to_string()]), 189 } 190 } 191 192 fn sample_document() -> Document { 193 Document { 194 d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(), 195 doc_type: "charter".to_string(), 196 title: "Charter".to_string(), 197 version: "1.0.0".to_string(), 198 summary: None, 199 effective_at: None, 200 body_markdown: None, 201 subject: DocumentSubject { 202 pubkey: TEST_PUBKEY_HEX.to_string(), 203 address: Some(format!("30340:{TEST_PUBKEY_HEX}:AAAAAAAAAAAAAAAAAAAAAA")), 204 }, 205 tags: Some(vec!["policy".to_string(), " ".to_string()]), 206 } 207 } 208 209 fn sample_plot() -> Plot { 210 Plot { 211 d_tag: "AAAAAAAAAAAAAAAAAAAABQ".to_string(), 212 farm: FarmRef { 213 pubkey: TEST_PUBKEY_HEX.to_string(), 214 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 215 }, 216 name: "Plot".to_string(), 217 about: None, 218 location: Some(PlotLocation { 219 primary: Some("plot".to_string()), 220 city: None, 221 region: None, 222 country: None, 223 gcs: sample_gcs(), 224 }), 225 tags: Some(vec!["shade-grown".to_string(), " ".to_string()]), 226 } 227 } 228 229 fn sample_resource_area() -> ResourceArea { 230 ResourceArea { 231 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 232 name: "Area".to_string(), 233 about: None, 234 location: ResourceAreaLocation { 235 primary: None, 236 city: None, 237 region: None, 238 country: None, 239 gcs: sample_gcs(), 240 }, 241 tags: Some(vec!["orchard".to_string(), " ".to_string()]), 242 } 243 } 244 245 fn sample_resource_cap() -> ResourceHarvestCap { 246 ResourceHarvestCap { 247 d_tag: "AAAAAAAAAAAAAAAAAAAABA".to_string(), 248 resource_area: ResourceAreaRef { 249 pubkey: TEST_PUBKEY_HEX.to_string(), 250 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 251 }, 252 product: ResourceHarvestProduct { 253 key: "nutmeg".to_string(), 254 category: Some("spice".to_string()), 255 }, 256 start: 1, 257 end: 2, 258 cap_quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(), 259 display_amount: None, 260 display_unit: None, 261 display_label: None, 262 tags: Some(vec!["seasonal".to_string(), " ".to_string()]), 263 } 264 } 265 266 #[test] 267 fn structured_wire_parts_cover_default_kind_and_error_paths() { 268 let farm = sample_farm(); 269 let wire = farm_to_wire_parts(&farm).unwrap(); 270 assert_eq!(wire.kind, KIND_FARM); 271 assert!(wire.tags.iter().any(|tag| tag[0] == "d")); 272 assert!(wire.content.contains("\"Farm\"")); 273 assert!(matches!( 274 farm_to_wire_parts_with_kind(&farm, KIND_COOP).unwrap_err(), 275 EventEncodeError::InvalidKind(KIND_COOP) 276 )); 277 let mut invalid_farm = farm.clone(); 278 invalid_farm.d_tag = " ".to_string(); 279 assert!(matches!( 280 farm_to_wire_parts(&invalid_farm).unwrap_err(), 281 EventEncodeError::EmptyRequiredField("d_tag") 282 )); 283 284 let coop = sample_coop(); 285 let wire = coop_to_wire_parts(&coop).unwrap(); 286 assert_eq!(wire.kind, KIND_COOP); 287 assert!(wire.tags.iter().any(|tag| tag[0] == "g")); 288 assert!(wire.content.contains("\"Coop\"")); 289 assert!(matches!( 290 coop_to_wire_parts_with_kind(&coop, KIND_FARM).unwrap_err(), 291 EventEncodeError::InvalidKind(KIND_FARM) 292 )); 293 let mut invalid_coop = coop.clone(); 294 invalid_coop.name = " ".to_string(); 295 assert!(matches!( 296 coop_to_wire_parts(&invalid_coop).unwrap_err(), 297 EventEncodeError::EmptyRequiredField("name") 298 )); 299 300 let document = sample_document(); 301 let wire = document_to_wire_parts(&document).unwrap(); 302 assert_eq!(wire.kind, KIND_DOCUMENT); 303 assert!(wire.tags.iter().any(|tag| tag[0] == "a")); 304 assert!(wire.content.contains("\"Charter\"")); 305 assert!(matches!( 306 document_to_wire_parts_with_kind(&document, KIND_FARM).unwrap_err(), 307 EventEncodeError::InvalidKind(KIND_FARM) 308 )); 309 let mut invalid_document = document.clone(); 310 invalid_document.subject.pubkey = " ".to_string(); 311 assert!(matches!( 312 document_to_wire_parts(&invalid_document).unwrap_err(), 313 EventEncodeError::EmptyRequiredField("subject.pubkey") 314 )); 315 316 let plot = sample_plot(); 317 let wire = plot_to_wire_parts(&plot).unwrap(); 318 assert_eq!(wire.kind, KIND_PLOT); 319 assert!(wire.tags.iter().any(|tag| tag[0] == "p")); 320 assert!(wire.content.contains("\"Plot\"")); 321 assert!(matches!( 322 plot_to_wire_parts_with_kind(&plot, KIND_FARM).unwrap_err(), 323 EventEncodeError::InvalidKind(KIND_FARM) 324 )); 325 let mut invalid_plot = plot.clone(); 326 invalid_plot.farm.pubkey = " ".to_string(); 327 assert!(matches!( 328 plot_to_wire_parts(&invalid_plot).unwrap_err(), 329 EventEncodeError::EmptyRequiredField("farm.pubkey") 330 )); 331 332 let area = sample_resource_area(); 333 let wire = resource_area_to_wire_parts(&area).unwrap(); 334 assert_eq!(wire.kind, KIND_RESOURCE_AREA); 335 assert!(wire.tags.iter().any(|tag| tag[0] == "g")); 336 assert!(wire.content.contains("\"Area\"")); 337 assert!(matches!( 338 resource_area_to_wire_parts_with_kind(&area, KIND_FARM).unwrap_err(), 339 EventEncodeError::InvalidKind(KIND_FARM) 340 )); 341 let mut invalid_area = area.clone(); 342 invalid_area.location.gcs.geohash = " ".to_string(); 343 assert!(matches!( 344 resource_area_to_wire_parts(&invalid_area).unwrap_err(), 345 EventEncodeError::EmptyRequiredField("location.gcs.geohash") 346 )); 347 348 let cap = sample_resource_cap(); 349 let wire = resource_cap_to_wire_parts(&cap).unwrap(); 350 assert_eq!(wire.kind, KIND_RESOURCE_HARVEST_CAP); 351 assert!(wire.tags.iter().any(|tag| tag[0] == "category")); 352 assert!(wire.content.contains("\"nutmeg\"")); 353 assert!(matches!( 354 resource_cap_to_wire_parts_with_kind(&cap, KIND_FARM).unwrap_err(), 355 EventEncodeError::InvalidKind(KIND_FARM) 356 )); 357 let mut invalid_cap = cap.clone(); 358 invalid_cap.resource_area.d_tag = " ".to_string(); 359 assert!(matches!( 360 resource_cap_to_wire_parts(&invalid_cap).unwrap_err(), 361 EventEncodeError::EmptyRequiredField("resource_area.d_tag") 362 )); 363 } 364 365 #[test] 366 fn structured_build_tags_cover_optional_and_error_paths() { 367 let farm = Farm { 368 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 369 name: "Farm".to_string(), 370 about: None, 371 website: None, 372 picture: None, 373 banner: None, 374 location: Some(FarmPublicLocation { 375 primary: "farm".to_string(), 376 city: Some("Town".to_string()), 377 region: Some("Region".to_string()), 378 country: None, 379 geohash: "9q8yy".to_string(), 380 }), 381 tags: Some(vec!["organic".to_string(), " ".to_string()]), 382 }; 383 let farm_tags = farm_build_tags(&farm).unwrap(); 384 assert!(farm_tags.iter().any(|tag| tag[0] == "d")); 385 assert!( 386 farm_tags 387 .iter() 388 .any(|tag| tag[0] == "t" && tag[1] == "organic") 389 ); 390 assert!(farm_tags.iter().any(|tag| tag[0] == "g")); 391 392 let mut invalid_farm = farm.clone(); 393 invalid_farm.location.as_mut().unwrap().geohash = " ".to_string(); 394 let err = farm_build_tags(&invalid_farm).unwrap_err(); 395 assert!(matches!( 396 err, 397 EventEncodeError::EmptyRequiredField("location.geohash") 398 )); 399 400 let mut string_only_farm = farm.clone(); 401 string_only_farm.location = None; 402 let string_only_tags = farm_build_tags(&string_only_farm).unwrap(); 403 assert!( 404 !string_only_tags 405 .iter() 406 .any(|tag| tag.first().map(|v| v.as_str()) == Some("g")) 407 ); 408 409 let farm_ref_tags = farm_ref_tags(&FarmRef { 410 pubkey: TEST_PUBKEY_HEX.to_string(), 411 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 412 }) 413 .unwrap(); 414 assert_eq!(farm_ref_tags.len(), 2); 415 416 let coop = Coop { 417 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 418 name: "Coop".to_string(), 419 about: None, 420 website: None, 421 picture: None, 422 banner: None, 423 location: Some(CoopLocation { 424 primary: Some("coop".to_string()), 425 city: None, 426 region: None, 427 country: None, 428 gcs: sample_gcs(), 429 }), 430 tags: Some(vec!["co-op".to_string(), " ".to_string()]), 431 }; 432 let coop_tags = coop_build_tags(&coop).unwrap(); 433 assert!(coop_tags.iter().any(|tag| tag[0] == "g")); 434 assert!( 435 coop_tags 436 .iter() 437 .any(|tag| tag[0] == "t" && tag[1] == "co-op") 438 ); 439 let coop_ref_tags = coop_ref_tags(&CoopRef { 440 pubkey: TEST_PUBKEY_HEX.to_string(), 441 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 442 }) 443 .unwrap(); 444 assert_eq!(coop_ref_tags.len(), 2); 445 446 let document = Document { 447 d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(), 448 doc_type: "charter".to_string(), 449 title: "Charter".to_string(), 450 version: "1.0.0".to_string(), 451 summary: None, 452 effective_at: None, 453 body_markdown: None, 454 subject: DocumentSubject { 455 pubkey: TEST_PUBKEY_HEX.to_string(), 456 address: Some(format!("30340:{TEST_PUBKEY_HEX}:AAAAAAAAAAAAAAAAAAAAAA")), 457 }, 458 tags: Some(vec!["policy".to_string(), " ".to_string()]), 459 }; 460 let doc_tags = document_build_tags(&document).unwrap(); 461 assert!(doc_tags.iter().any(|tag| tag[0] == "p")); 462 assert!(doc_tags.iter().any(|tag| tag[0] == "a")); 463 assert!( 464 doc_tags 465 .iter() 466 .any(|tag| tag[0] == "t" && tag[1] == "policy") 467 ); 468 469 let mut invalid_document = document.clone(); 470 invalid_document.subject.address = Some(" ".to_string()); 471 let err = document_build_tags(&invalid_document).unwrap_err(); 472 assert!(matches!( 473 err, 474 EventEncodeError::EmptyRequiredField("subject.address") 475 )); 476 477 let plot = Plot { 478 d_tag: "AAAAAAAAAAAAAAAAAAAABQ".to_string(), 479 farm: FarmRef { 480 pubkey: TEST_PUBKEY_HEX.to_string(), 481 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 482 }, 483 name: "Plot".to_string(), 484 about: None, 485 location: Some(PlotLocation { 486 primary: Some("plot".to_string()), 487 city: None, 488 region: None, 489 country: None, 490 gcs: sample_gcs(), 491 }), 492 tags: Some(vec!["shade-grown".to_string(), " ".to_string()]), 493 }; 494 let plot_tags = plot_build_tags(&plot).unwrap(); 495 assert!(plot_tags.iter().any(|tag| tag[0] == "a")); 496 assert!(plot_tags.iter().any(|tag| tag[0] == "p")); 497 assert!(plot_tags.iter().any(|tag| tag[0] == "g")); 498 assert!( 499 plot_tags 500 .iter() 501 .any(|tag| tag[0] == "t" && tag[1] == "shade-grown") 502 ); 503 504 let mut invalid_plot = plot.clone(); 505 invalid_plot.location.as_mut().unwrap().gcs.geohash = " ".to_string(); 506 let err = plot_build_tags(&invalid_plot).unwrap_err(); 507 assert!(matches!( 508 err, 509 EventEncodeError::EmptyRequiredField("location.gcs.geohash") 510 )); 511 512 let err = plot_address("", "AAAAAAAAAAAAAAAAAAAABQ").unwrap_err(); 513 assert!(matches!( 514 err, 515 EventEncodeError::EmptyRequiredField("plot.author_pubkey") 516 )); 517 518 let area = ResourceArea { 519 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 520 name: "Area".to_string(), 521 about: None, 522 location: ResourceAreaLocation { 523 primary: None, 524 city: None, 525 region: None, 526 country: None, 527 gcs: sample_gcs(), 528 }, 529 tags: Some(vec!["orchard".to_string(), " ".to_string()]), 530 }; 531 let area_tags = resource_area_build_tags(&area).unwrap(); 532 assert!(area_tags.iter().any(|tag| tag[0] == "d")); 533 assert!(area_tags.iter().any(|tag| tag[0] == "g")); 534 assert!( 535 area_tags 536 .iter() 537 .any(|tag| tag[0] == "t" && tag[1] == "orchard") 538 ); 539 let area_ref_tags = resource_area_ref_tags(&ResourceAreaRef { 540 pubkey: TEST_PUBKEY_HEX.to_string(), 541 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 542 }) 543 .unwrap(); 544 assert_eq!(area_ref_tags.len(), 2); 545 546 let mut invalid_area = area.clone(); 547 invalid_area.location.gcs.geohash = " ".to_string(); 548 let err = resource_area_build_tags(&invalid_area).unwrap_err(); 549 assert!(matches!( 550 err, 551 EventEncodeError::EmptyRequiredField("location.gcs.geohash") 552 )); 553 554 let cap = ResourceHarvestCap { 555 d_tag: "AAAAAAAAAAAAAAAAAAAABA".to_string(), 556 resource_area: ResourceAreaRef { 557 pubkey: TEST_PUBKEY_HEX.to_string(), 558 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 559 }, 560 product: ResourceHarvestProduct { 561 key: "nutmeg".to_string(), 562 category: Some("spice".to_string()), 563 }, 564 start: 1, 565 end: 2, 566 cap_quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(), 567 display_amount: None, 568 display_unit: None, 569 display_label: None, 570 tags: Some(vec!["seasonal".to_string(), " ".to_string()]), 571 }; 572 let cap_tags = resource_harvest_cap_build_tags(&cap).unwrap(); 573 assert!( 574 cap_tags 575 .iter() 576 .any(|tag| tag[0] == "category" && tag[1] == "spice") 577 ); 578 assert!( 579 cap_tags 580 .iter() 581 .any(|tag| tag[0] == "t" && tag[1] == "seasonal") 582 ); 583 584 let mut invalid_cap = cap.clone(); 585 invalid_cap.product.key = " ".to_string(); 586 let err = resource_harvest_cap_build_tags(&invalid_cap).unwrap_err(); 587 assert!(matches!( 588 err, 589 EventEncodeError::EmptyRequiredField("product.key") 590 )); 591 } 592 593 #[test] 594 fn structured_build_tags_cover_required_field_errors() { 595 let document = Document { 596 d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(), 597 doc_type: "charter".to_string(), 598 title: "Charter".to_string(), 599 version: "1.0.0".to_string(), 600 summary: None, 601 effective_at: None, 602 body_markdown: None, 603 subject: DocumentSubject { 604 pubkey: TEST_PUBKEY_HEX.to_string(), 605 address: Some(format!("30340:{TEST_PUBKEY_HEX}:AAAAAAAAAAAAAAAAAAAAAA")), 606 }, 607 tags: None, 608 }; 609 let document_tags = document_build_tags(&document).unwrap(); 610 assert!(document_tags.iter().any(|tag| tag[0] == "a")); 611 612 let mut invalid_document = document.clone(); 613 invalid_document.d_tag = " ".to_string(); 614 let err = document_build_tags(&invalid_document).unwrap_err(); 615 assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag"))); 616 invalid_document = document.clone(); 617 invalid_document.doc_type = " ".to_string(); 618 let err = document_build_tags(&invalid_document).unwrap_err(); 619 assert!(matches!( 620 err, 621 EventEncodeError::EmptyRequiredField("doc_type") 622 )); 623 invalid_document = document.clone(); 624 invalid_document.title = " ".to_string(); 625 let err = document_build_tags(&invalid_document).unwrap_err(); 626 assert!(matches!(err, EventEncodeError::EmptyRequiredField("title"))); 627 invalid_document = document.clone(); 628 invalid_document.version = " ".to_string(); 629 let err = document_build_tags(&invalid_document).unwrap_err(); 630 assert!(matches!( 631 err, 632 EventEncodeError::EmptyRequiredField("version") 633 )); 634 invalid_document = document.clone(); 635 invalid_document.subject.pubkey = " ".to_string(); 636 let err = document_build_tags(&invalid_document).unwrap_err(); 637 assert!(matches!( 638 err, 639 EventEncodeError::EmptyRequiredField("subject.pubkey") 640 )); 641 642 let farm = Farm { 643 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 644 name: "Farm".to_string(), 645 about: None, 646 website: None, 647 picture: None, 648 banner: None, 649 location: None, 650 tags: None, 651 }; 652 let mut invalid_farm = farm.clone(); 653 invalid_farm.d_tag = " ".to_string(); 654 let err = farm_build_tags(&invalid_farm).unwrap_err(); 655 assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag"))); 656 invalid_farm = farm.clone(); 657 invalid_farm.name = " ".to_string(); 658 let err = farm_build_tags(&invalid_farm).unwrap_err(); 659 assert!(matches!(err, EventEncodeError::EmptyRequiredField("name"))); 660 let err = farm_ref_tags(&FarmRef { 661 pubkey: " ".to_string(), 662 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 663 }) 664 .unwrap_err(); 665 assert!(matches!( 666 err, 667 EventEncodeError::EmptyRequiredField("farm.pubkey") 668 )); 669 let err = farm_ref_tags(&FarmRef { 670 pubkey: TEST_PUBKEY_HEX.to_string(), 671 d_tag: " ".to_string(), 672 }) 673 .unwrap_err(); 674 assert!(matches!( 675 err, 676 EventEncodeError::EmptyRequiredField("farm.d_tag") 677 )); 678 679 let coop = Coop { 680 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 681 name: "Coop".to_string(), 682 about: None, 683 website: None, 684 picture: None, 685 banner: None, 686 location: None, 687 tags: None, 688 }; 689 let mut invalid_coop = coop.clone(); 690 invalid_coop.d_tag = " ".to_string(); 691 let err = coop_build_tags(&invalid_coop).unwrap_err(); 692 assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag"))); 693 invalid_coop = coop.clone(); 694 invalid_coop.name = " ".to_string(); 695 let err = coop_build_tags(&invalid_coop).unwrap_err(); 696 assert!(matches!(err, EventEncodeError::EmptyRequiredField("name"))); 697 invalid_coop = coop.clone(); 698 invalid_coop.location = Some(CoopLocation { 699 primary: None, 700 city: None, 701 region: None, 702 country: None, 703 gcs: GcsLocation { 704 geohash: " ".to_string(), 705 ..sample_gcs() 706 }, 707 }); 708 let err = coop_build_tags(&invalid_coop).unwrap_err(); 709 assert!(matches!( 710 err, 711 EventEncodeError::EmptyRequiredField("location.gcs.geohash") 712 )); 713 let err = coop_ref_tags(&CoopRef { 714 pubkey: " ".to_string(), 715 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 716 }) 717 .unwrap_err(); 718 assert!(matches!( 719 err, 720 EventEncodeError::EmptyRequiredField("coop.pubkey") 721 )); 722 let err = coop_ref_tags(&CoopRef { 723 pubkey: TEST_PUBKEY_HEX.to_string(), 724 d_tag: " ".to_string(), 725 }) 726 .unwrap_err(); 727 assert!(matches!( 728 err, 729 EventEncodeError::EmptyRequiredField("coop.d_tag") 730 )); 731 732 let plot = Plot { 733 d_tag: "AAAAAAAAAAAAAAAAAAAABQ".to_string(), 734 farm: FarmRef { 735 pubkey: TEST_PUBKEY_HEX.to_string(), 736 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 737 }, 738 name: "Plot".to_string(), 739 about: None, 740 location: None, 741 tags: None, 742 }; 743 let mut invalid_plot = plot.clone(); 744 invalid_plot.d_tag = " ".to_string(); 745 let err = plot_build_tags(&invalid_plot).unwrap_err(); 746 assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag"))); 747 invalid_plot = plot.clone(); 748 invalid_plot.name = " ".to_string(); 749 let err = plot_build_tags(&invalid_plot).unwrap_err(); 750 assert!(matches!(err, EventEncodeError::EmptyRequiredField("name"))); 751 invalid_plot = plot.clone(); 752 invalid_plot.farm.pubkey = " ".to_string(); 753 let err = plot_build_tags(&invalid_plot).unwrap_err(); 754 assert!(matches!( 755 err, 756 EventEncodeError::EmptyRequiredField("farm.pubkey") 757 )); 758 invalid_plot = plot.clone(); 759 invalid_plot.farm.d_tag = " ".to_string(); 760 let err = plot_build_tags(&invalid_plot).unwrap_err(); 761 assert!(matches!( 762 err, 763 EventEncodeError::EmptyRequiredField("farm.d_tag") 764 )); 765 let err = plot_address(TEST_PUBKEY_HEX, " ").unwrap_err(); 766 assert!(matches!( 767 err, 768 EventEncodeError::EmptyRequiredField("plot.d_tag") 769 )); 770 771 let area = ResourceArea { 772 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 773 name: "Area".to_string(), 774 about: None, 775 location: ResourceAreaLocation { 776 primary: None, 777 city: None, 778 region: None, 779 country: None, 780 gcs: sample_gcs(), 781 }, 782 tags: None, 783 }; 784 let mut invalid_area = area.clone(); 785 invalid_area.d_tag = " ".to_string(); 786 let err = resource_area_build_tags(&invalid_area).unwrap_err(); 787 assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag"))); 788 invalid_area = area.clone(); 789 invalid_area.name = " ".to_string(); 790 let err = resource_area_build_tags(&invalid_area).unwrap_err(); 791 assert!(matches!(err, EventEncodeError::EmptyRequiredField("name"))); 792 let err = resource_area_ref_tags(&ResourceAreaRef { 793 pubkey: " ".to_string(), 794 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 795 }) 796 .unwrap_err(); 797 assert!(matches!( 798 err, 799 EventEncodeError::EmptyRequiredField("resource_area.pubkey") 800 )); 801 let err = resource_area_ref_tags(&ResourceAreaRef { 802 pubkey: TEST_PUBKEY_HEX.to_string(), 803 d_tag: " ".to_string(), 804 }) 805 .unwrap_err(); 806 assert!(matches!( 807 err, 808 EventEncodeError::EmptyRequiredField("resource_area.d_tag") 809 )); 810 811 let cap = ResourceHarvestCap { 812 d_tag: "AAAAAAAAAAAAAAAAAAAABA".to_string(), 813 resource_area: ResourceAreaRef { 814 pubkey: TEST_PUBKEY_HEX.to_string(), 815 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 816 }, 817 product: ResourceHarvestProduct { 818 key: "nutmeg".to_string(), 819 category: Some("spice".to_string()), 820 }, 821 start: 1, 822 end: 2, 823 cap_quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG).unwrap(), 824 display_amount: None, 825 display_unit: None, 826 display_label: None, 827 tags: None, 828 }; 829 let mut invalid_cap = cap.clone(); 830 invalid_cap.d_tag = " ".to_string(); 831 let err = resource_harvest_cap_build_tags(&invalid_cap).unwrap_err(); 832 assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag"))); 833 invalid_cap = cap.clone(); 834 invalid_cap.resource_area.pubkey = " ".to_string(); 835 let err = resource_harvest_cap_build_tags(&invalid_cap).unwrap_err(); 836 assert!(matches!( 837 err, 838 EventEncodeError::EmptyRequiredField("resource_area.pubkey") 839 )); 840 invalid_cap = cap.clone(); 841 invalid_cap.resource_area.d_tag = " ".to_string(); 842 let err = resource_harvest_cap_build_tags(&invalid_cap).unwrap_err(); 843 assert!(matches!( 844 err, 845 EventEncodeError::EmptyRequiredField("resource_area.d_tag") 846 )); 847 let mut no_category = cap.clone(); 848 no_category.product.category = Some(" ".to_string()); 849 let tags = resource_harvest_cap_build_tags(&no_category).unwrap(); 850 assert!(!tags.iter().any(|tag| tag[0] == "category")); 851 } 852 853 #[test] 854 fn structured_list_sets_cover_success_and_error_paths() { 855 let farm_id = "AAAAAAAAAAAAAAAAAAAAAA"; 856 let members = farm_members_list_set(farm_id, [TEST_PUBKEY_HEX]).unwrap(); 857 assert_eq!(members.d_tag, format!("farm:{farm_id}:members")); 858 let owners = farm_owners_list_set(farm_id, [TEST_PUBKEY_HEX]).unwrap(); 859 assert_eq!(owners.d_tag, format!("farm:{farm_id}:members.owners")); 860 let workers = farm_workers_list_set(farm_id, [TEST_PUBKEY_HEX]).unwrap(); 861 assert_eq!(workers.d_tag, format!("farm:{farm_id}:members.workers")); 862 863 let plots = farm_plots_list_set(farm_id, TEST_PUBKEY_HEX, ["AAAAAAAAAAAAAAAAAAAABQ"]).unwrap(); 864 assert_eq!(plots.d_tag, format!("farm:{farm_id}:plots")); 865 assert_eq!(plots.entries.len(), 1); 866 867 let listings = 868 farm_operational_listings_list_set(farm_id, TEST_PUBKEY_HEX, ["AAAAAAAAAAAAAAAAAAAAAg"]) 869 .unwrap(); 870 assert_eq!(listings.d_tag, format!("farm:{farm_id}:listings")); 871 assert_eq!(listings.entries.len(), 1); 872 873 let listings_from = farm_operational_listings_list_set_from_listings( 874 farm_id, 875 TEST_PUBKEY_HEX, 876 [sample_listing("AAAAAAAAAAAAAAAAAAAAAg")].iter(), 877 ) 878 .unwrap(); 879 assert_eq!(listings_from.entries.len(), 1); 880 881 let plots_from = farm_plots_list_set_from_plots( 882 farm_id, 883 TEST_PUBKEY_HEX, 884 [Plot { 885 d_tag: "AAAAAAAAAAAAAAAAAAAABQ".to_string(), 886 farm: FarmRef { 887 pubkey: TEST_PUBKEY_HEX.to_string(), 888 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 889 }, 890 name: "plot".to_string(), 891 about: None, 892 location: None, 893 tags: None, 894 }] 895 .iter(), 896 ) 897 .unwrap(); 898 assert_eq!(plots_from.entries.len(), 1); 899 900 let member_of_farms = member_of_farms_list_set([TEST_PUBKEY_HEX]).unwrap(); 901 assert_eq!(member_of_farms.d_tag, "member_of.farms"); 902 903 let err = farm_members_list_set("", [TEST_PUBKEY_HEX]).unwrap_err(); 904 assert!(matches!( 905 err, 906 EventEncodeError::EmptyRequiredField("farm_id") 907 )); 908 let err = farm_members_list_set(farm_id, [" "]).unwrap_err(); 909 assert!(matches!( 910 err, 911 EventEncodeError::EmptyRequiredField("entry.values") 912 )); 913 let err = farm_operational_listings_list_set(farm_id, TEST_PUBKEY_HEX, [" "]).unwrap_err(); 914 assert!(matches!( 915 err, 916 EventEncodeError::EmptyRequiredField("listing_id") 917 )); 918 919 let coop_id = "AAAAAAAAAAAAAAAAAAAAAQ"; 920 let coop_members = coop_members_list_set(coop_id, [TEST_PUBKEY_HEX]).unwrap(); 921 assert_eq!(coop_members.d_tag, format!("coop:{coop_id}:members")); 922 let coop_owners = coop_owners_list_set(coop_id, [TEST_PUBKEY_HEX]).unwrap(); 923 assert_eq!(coop_owners.d_tag, format!("coop:{coop_id}:members.owners")); 924 let coop_admins = coop_admins_list_set(coop_id, [TEST_PUBKEY_HEX]).unwrap(); 925 assert_eq!(coop_admins.d_tag, format!("coop:{coop_id}:members.admins")); 926 let coop_items = coop_items_list_set(coop_id, ["30340:pubkey:AAAAAAAAAAAAAAAAAAAAAA"]).unwrap(); 927 assert_eq!(coop_items.d_tag, format!("coop:{coop_id}:items")); 928 let member_of_coops = member_of_coops_list_set([TEST_PUBKEY_HEX]).unwrap(); 929 assert_eq!(member_of_coops.d_tag, "member_of.coops"); 930 931 let coop_farms = coop_members_farms_list_set( 932 coop_id, 933 [FarmRef { 934 pubkey: TEST_PUBKEY_HEX.to_string(), 935 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 936 }], 937 ) 938 .unwrap(); 939 assert_eq!(coop_farms.entries.len(), 2); 940 941 let err = coop_members_list_set("", [TEST_PUBKEY_HEX]).unwrap_err(); 942 assert!(matches!( 943 err, 944 EventEncodeError::EmptyRequiredField("coop_id") 945 )); 946 let err = coop_members_list_set(coop_id, [" "]).unwrap_err(); 947 assert!(matches!( 948 err, 949 EventEncodeError::EmptyRequiredField("entry.values") 950 )); 951 let err = coop_members_farms_list_set( 952 coop_id, 953 [FarmRef { 954 pubkey: "".to_string(), 955 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 956 }], 957 ) 958 .unwrap_err(); 959 assert!(matches!( 960 err, 961 EventEncodeError::EmptyRequiredField("farm.pubkey") 962 )); 963 let err = coop_members_farms_list_set( 964 "invalid", 965 [FarmRef { 966 pubkey: TEST_PUBKEY_HEX.to_string(), 967 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 968 }], 969 ) 970 .unwrap_err(); 971 assert!(matches!(err, EventEncodeError::InvalidField("coop_id"))); 972 973 let area_id = "AAAAAAAAAAAAAAAAAAAAAw"; 974 let resource_farms = resource_area_members_farms_list_set( 975 area_id, 976 [FarmRef { 977 pubkey: TEST_PUBKEY_HEX.to_string(), 978 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 979 }], 980 ) 981 .unwrap(); 982 assert_eq!(resource_farms.entries.len(), 2); 983 984 let resource_plots = resource_area_members_plots_list_set( 985 area_id, 986 [PlotRef { 987 pubkey: TEST_PUBKEY_HEX.to_string(), 988 d_tag: "AAAAAAAAAAAAAAAAAAAABQ".to_string(), 989 }], 990 ) 991 .unwrap(); 992 assert_eq!(resource_plots.entries.len(), 2); 993 994 let resource_stewards = resource_area_stewards_list_set(area_id, [TEST_PUBKEY_HEX]).unwrap(); 995 assert_eq!(resource_stewards.entries.len(), 1); 996 997 let err = resource_area_stewards_list_set("", [TEST_PUBKEY_HEX]).unwrap_err(); 998 assert!(matches!( 999 err, 1000 EventEncodeError::EmptyRequiredField("area_id") 1001 )); 1002 let err = resource_area_stewards_list_set(area_id, [" "]).unwrap_err(); 1003 assert!(matches!( 1004 err, 1005 EventEncodeError::EmptyRequiredField("entry.values") 1006 )); 1007 let err = resource_area_members_plots_list_set( 1008 area_id, 1009 [PlotRef { 1010 pubkey: "".to_string(), 1011 d_tag: "AAAAAAAAAAAAAAAAAAAABQ".to_string(), 1012 }], 1013 ) 1014 .unwrap_err(); 1015 assert!(matches!( 1016 err, 1017 EventEncodeError::EmptyRequiredField("plot.pubkey") 1018 )); 1019 } 1020 1021 #[test] 1022 fn structured_list_set_outputs_remain_deterministic() { 1023 let list_set: ListSet = 1024 farm_members_list_set("AAAAAAAAAAAAAAAAAAAAAA", [TEST_PUBKEY_HEX, TEST_PUBKEY_HEX]) 1025 .unwrap(); 1026 assert_eq!(list_set.entries.len(), 2); 1027 assert_eq!(list_set.entries[0].tag, "p"); 1028 }