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