emit.rs (71346B)
1 #[cfg(not(feature = "std"))] 2 use alloc::format; 3 #[cfg(not(feature = "std"))] 4 use alloc::{ 5 collections::BTreeMap, 6 string::{String, ToString}, 7 vec::Vec, 8 }; 9 #[cfg(feature = "std")] 10 use std::collections::BTreeMap; 11 12 use crate::canonical::canonical_json_string; 13 use crate::error::RadrootsReplicaEventsError; 14 use crate::geo::{geojson_point_from_lat_lng, geojson_polygon_circle_wgs84}; 15 use crate::types::{ 16 RADROOTS_REPLICA_TRANSFER_VERSION, RadrootsReplicaEventDraft, RadrootsReplicaFarmSelector, 17 RadrootsReplicaSyncBundle, RadrootsReplicaSyncOptions, RadrootsReplicaSyncRequest, 18 }; 19 use radroots_event::envelope::kind::{KIND_FARM, KIND_LIST_SET_GENERIC, KIND_PLOT}; 20 use radroots_event::farm::change_set::{ 21 GcsLocation as EventGcsLocation, GeoJsonPoint, GeoJsonPolygon, 22 }; 23 use radroots_event::farm::location::{has_textual_locality, is_public_geohash5}; 24 use radroots_event::farm::plot::Plot as EventPlot; 25 use radroots_event::farm::{Farm as EventFarm, FarmPublicLocation, FarmRef}; 26 use radroots_event::wire::Nip01EventWireParts; 27 use radroots_event_codec::encode::farm as farm_encode; 28 use radroots_event_codec::encode::farm as farm_list_sets; 29 use radroots_event_codec::encode::list_set as list_set_encode; 30 use radroots_event_codec::encode::plot as plot_encode; 31 use radroots_replica_schema::farm::{ 32 Farm, IFarmFieldsFilter, IFarmFindMany, IFarmFindOne, IFarmFindOneArgs, 33 }; 34 use radroots_replica_schema::farm_gcs_location::{ 35 FarmGcsLocation, IFarmGcsLocationFieldsFilter, IFarmGcsLocationFindMany, 36 }; 37 use radroots_replica_schema::farm_member::{ 38 FarmMember, IFarmMemberFieldsFilter, IFarmMemberFindMany, 39 }; 40 use radroots_replica_schema::farm_member_claim::{ 41 FarmMemberClaim, IFarmMemberClaimFieldsFilter, IFarmMemberClaimFindMany, 42 }; 43 use radroots_replica_schema::farm_tag::{IFarmTagFieldsFilter, IFarmTagFindMany}; 44 use radroots_replica_schema::gcs_location::{ 45 GcsLocation, GcsLocationQueryBindValues, IGcsLocationFindOne, IGcsLocationFindOneArgs, 46 }; 47 use radroots_replica_schema::plot::{IPlotFieldsFilter, IPlotFindMany, Plot}; 48 use radroots_replica_schema::plot_gcs_location::{ 49 IPlotGcsLocationFieldsFilter, IPlotGcsLocationFindMany, PlotGcsLocation, 50 }; 51 use radroots_replica_schema::plot_tag::{IPlotTagFieldsFilter, IPlotTagFindMany}; 52 use radroots_replica_store::{ 53 farm, farm_gcs_location, farm_member, farm_member_claim, farm_tag, gcs_location, plot, 54 plot_gcs_location, plot_tag, 55 }; 56 use radroots_sql_core::SqlExecutor; 57 58 const ROLE_PRIMARY: &str = "primary"; 59 const ROLE_MEMBER: &str = "member"; 60 const ROLE_OWNER: &str = "owner"; 61 const ROLE_WORKER: &str = "worker"; 62 63 #[cfg(test)] 64 pub(crate) mod failpoints { 65 use std::cell::Cell; 66 67 thread_local! { 68 static FORCE_LIST_SET_TO_WIRE_ERROR: Cell<bool> = const { Cell::new(false) }; 69 static FORCE_GCS_LOCATION_TO_EVENT_ERROR: Cell<bool> = const { Cell::new(false) }; 70 } 71 72 pub(crate) fn set_list_set_to_wire_error() { 73 FORCE_LIST_SET_TO_WIRE_ERROR.with(|flag| flag.set(true)); 74 } 75 76 pub(crate) fn take_list_set_to_wire_error() -> bool { 77 FORCE_LIST_SET_TO_WIRE_ERROR.with(|flag| { 78 let value = flag.get(); 79 flag.set(false); 80 value 81 }) 82 } 83 84 pub(crate) fn set_gcs_location_to_event_error() { 85 FORCE_GCS_LOCATION_TO_EVENT_ERROR.with(|flag| flag.set(true)); 86 } 87 88 pub(crate) fn take_gcs_location_to_event_error() -> bool { 89 FORCE_GCS_LOCATION_TO_EVENT_ERROR.with(|flag| { 90 let value = flag.get(); 91 flag.set(false); 92 value 93 }) 94 } 95 } 96 97 /// Builds the full replica transfer bundle. 98 /// 99 /// Profile events are intentionally excluded. Ingested Profile rows are lossy 100 /// projections and cannot prove author intent for a complete kind-0 snapshot. 101 pub fn radroots_replica_sync_all( 102 exec: &dyn SqlExecutor, 103 request: &RadrootsReplicaSyncRequest, 104 ) -> Result<RadrootsReplicaSyncBundle, RadrootsReplicaEventsError> { 105 radroots_replica_sync_all_with_options(exec, &request.farm, request.options.as_ref()) 106 } 107 108 /// Builds a replica transfer bundle using explicit inclusion options. 109 /// 110 /// Profile events are intentionally excluded from replica emission. 111 pub fn radroots_replica_sync_all_with_options( 112 exec: &dyn SqlExecutor, 113 farm_selector: &RadrootsReplicaFarmSelector, 114 options: Option<&RadrootsReplicaSyncOptions>, 115 ) -> Result<RadrootsReplicaSyncBundle, RadrootsReplicaEventsError> { 116 let farm = resolve_farm(exec, farm_selector)?; 117 let include_list_sets = options 118 .and_then(|opt| opt.include_list_sets) 119 .unwrap_or(true); 120 let include_claims = options 121 .and_then(|opt| opt.include_membership_claims) 122 .unwrap_or(true); 123 124 let mut events = Vec::new(); 125 126 events.push(radroots_replica_farm_event(exec, &farm)?); 127 128 let plots = radroots_replica_plot_events(exec, &farm)?; 129 events.extend(plots); 130 131 if include_list_sets { 132 let list_sets = radroots_replica_list_set_events(exec, &farm)?; 133 events.extend(list_sets); 134 } 135 136 if include_claims { 137 let claims = radroots_replica_membership_claim_events(exec, &farm.pubkey)?; 138 events.extend(claims); 139 } 140 141 Ok(RadrootsReplicaSyncBundle { 142 version: RADROOTS_REPLICA_TRANSFER_VERSION, 143 events, 144 }) 145 } 146 147 pub fn radroots_replica_farm_event( 148 exec: &dyn SqlExecutor, 149 farm: &Farm, 150 ) -> Result<RadrootsReplicaEventDraft, RadrootsReplicaEventsError> { 151 let tags = collect_farm_tags(exec, &farm.id)?; 152 let location = load_farm_location(exec, farm)?; 153 let farm_event = EventFarm { 154 d_tag: farm.d_tag.clone(), 155 name: farm.name.clone(), 156 about: farm.about.clone(), 157 website: farm.website.clone(), 158 picture: farm.picture.clone(), 159 banner: farm.banner.clone(), 160 location, 161 tags: if tags.is_empty() { None } else { Some(tags) }, 162 }; 163 let tags = farm_encode::farm_build_tags(&farm_event)?; 164 let content = canonical_json_string(&farm_event)?; 165 let parts = Nip01EventWireParts { 166 kind: KIND_FARM, 167 content, 168 tags, 169 }; 170 Ok(parts_to_draft(&farm.pubkey, parts)) 171 } 172 173 pub fn radroots_replica_plot_events( 174 exec: &dyn SqlExecutor, 175 farm: &Farm, 176 ) -> Result<Vec<RadrootsReplicaEventDraft>, RadrootsReplicaEventsError> { 177 let plots = load_plots(exec, &farm.id)?; 178 let mut events = Vec::new(); 179 for plot_row in plots { 180 let tags = collect_plot_tags(exec, &plot_row.id)?; 181 let location = load_plot_location(exec, &plot_row)?; 182 let plot_event = EventPlot { 183 d_tag: plot_row.d_tag.clone(), 184 farm: FarmRef { 185 pubkey: farm.pubkey.clone(), 186 d_tag: farm.d_tag.clone(), 187 }, 188 name: plot_row.name.clone(), 189 about: plot_row.about.clone(), 190 location, 191 tags: if tags.is_empty() { None } else { Some(tags) }, 192 }; 193 let tags = plot_encode::plot_build_tags(&plot_event)?; 194 let content = canonical_json_string(&plot_event)?; 195 let parts = Nip01EventWireParts { 196 kind: KIND_PLOT, 197 content, 198 tags, 199 }; 200 events.push(parts_to_draft(&farm.pubkey, parts)); 201 } 202 Ok(events) 203 } 204 205 pub fn radroots_replica_list_set_events( 206 exec: &dyn SqlExecutor, 207 farm: &Farm, 208 ) -> Result<Vec<RadrootsReplicaEventDraft>, RadrootsReplicaEventsError> { 209 let members = load_farm_members(exec, &farm.id)?; 210 let plots = load_plots(exec, &farm.id)?; 211 212 let members_list = 213 farm_list_sets::farm_members_list_set(&farm.d_tag, role_pubkeys(&members, ROLE_MEMBER))?; 214 let owners_list = 215 farm_list_sets::farm_owners_list_set(&farm.d_tag, role_pubkeys(&members, ROLE_OWNER))?; 216 let workers_list = 217 farm_list_sets::farm_workers_list_set(&farm.d_tag, role_pubkeys(&members, ROLE_WORKER))?; 218 219 let plot_ids = sorted_plot_ids(&plots); 220 let plots_list = farm_list_sets::farm_plots_list_set(&farm.d_tag, &farm.pubkey, plot_ids)?; 221 222 let list_sets = [members_list, owners_list, workers_list, plots_list]; 223 let mut events = Vec::new(); 224 for list_set in list_sets { 225 let parts = list_set_to_wire_parts(&list_set)?; 226 events.push(parts_to_draft(&farm.pubkey, parts)); 227 } 228 Ok(events) 229 } 230 231 pub fn radroots_replica_membership_claim_events( 232 exec: &dyn SqlExecutor, 233 farm_pubkey: &str, 234 ) -> Result<Vec<RadrootsReplicaEventDraft>, RadrootsReplicaEventsError> { 235 let claims = load_member_claims(exec, farm_pubkey)?; 236 let mut by_member: BTreeMap<String, Vec<String>> = BTreeMap::new(); 237 for claim in claims { 238 by_member 239 .entry(claim.member_pubkey.clone()) 240 .or_default() 241 .push(claim.farm_pubkey.clone()); 242 } 243 244 let mut events = Vec::new(); 245 for member_pubkey in by_member.keys() { 246 let all_claims = load_member_claims_for_member(exec, member_pubkey)?; 247 let mut farm_pubkeys = all_claims 248 .into_iter() 249 .map(|claim| claim.farm_pubkey) 250 .collect::<Vec<String>>(); 251 farm_pubkeys.sort(); 252 farm_pubkeys.dedup(); 253 let list_set = farm_list_sets::member_of_farms_list_set(farm_pubkeys)?; 254 let parts = list_set_to_wire_parts(&list_set)?; 255 events.push(parts_to_draft(member_pubkey, parts)); 256 } 257 258 Ok(events) 259 } 260 261 fn resolve_farm( 262 exec: &dyn SqlExecutor, 263 selector: &RadrootsReplicaFarmSelector, 264 ) -> Result<Farm, RadrootsReplicaEventsError> { 265 if let Some(id) = selector.id.as_ref().filter(|v| !v.trim().is_empty()) { 266 let result_query = farm::find_one( 267 exec, 268 &IFarmFindOne::On(IFarmFindOneArgs { 269 on: radroots_replica_schema::farm::FarmQueryBindValues::Id { id: id.clone() }, 270 }), 271 ); 272 let result = result_query?; 273 return result.result.ok_or_else(|| { 274 RadrootsReplicaEventsError::InvalidSelector(format!("farm not found: {id}")) 275 }); 276 } 277 278 let d_tag = selector 279 .d_tag 280 .as_ref() 281 .map(|v| v.trim()) 282 .filter(|v| !v.is_empty()); 283 let pubkey = selector 284 .pubkey 285 .as_ref() 286 .map(|v| v.trim()) 287 .filter(|v| !v.is_empty()); 288 289 let (d_tag, pubkey) = match (d_tag, pubkey) { 290 (Some(d_tag), Some(pubkey)) => (d_tag, pubkey), 291 _ => { 292 return Err(RadrootsReplicaEventsError::InvalidSelector( 293 "farm selector requires id or (d_tag + pubkey)".to_string(), 294 )); 295 } 296 }; 297 298 let filter = IFarmFieldsFilter { 299 id: None, 300 created_at: None, 301 updated_at: None, 302 d_tag: Some(d_tag.to_string()), 303 pubkey: Some(pubkey.to_string()), 304 name: None, 305 about: None, 306 website: None, 307 picture: None, 308 banner: None, 309 location_primary: None, 310 location_city: None, 311 location_region: None, 312 location_country: None, 313 }; 314 let result_query = farm::find_many( 315 exec, 316 &IFarmFindMany { 317 filter: Some(filter), 318 }, 319 ); 320 let result = result_query?; 321 if result.results.len() == 1 { 322 return Ok(result.results.into_iter().next().expect("farm result")); 323 } 324 Err(RadrootsReplicaEventsError::InvalidSelector( 325 "farm selector did not resolve to a single farm".to_string(), 326 )) 327 } 328 329 fn collect_farm_tags( 330 exec: &dyn SqlExecutor, 331 farm_id: &str, 332 ) -> Result<Vec<String>, RadrootsReplicaEventsError> { 333 let filter = IFarmTagFieldsFilter { 334 id: None, 335 created_at: None, 336 updated_at: None, 337 farm_id: Some(farm_id.to_string()), 338 tag: None, 339 }; 340 let result_query = farm_tag::find_many( 341 exec, 342 &IFarmTagFindMany { 343 filter: Some(filter), 344 }, 345 ); 346 let result = result_query?; 347 let mut tags = result 348 .results 349 .into_iter() 350 .map(|row| row.tag) 351 .collect::<Vec<_>>(); 352 tags.sort(); 353 tags.dedup(); 354 Ok(tags) 355 } 356 357 fn collect_plot_tags( 358 exec: &dyn SqlExecutor, 359 plot_id: &str, 360 ) -> Result<Vec<String>, RadrootsReplicaEventsError> { 361 let filter = IPlotTagFieldsFilter { 362 id: None, 363 created_at: None, 364 updated_at: None, 365 plot_id: Some(plot_id.to_string()), 366 tag: None, 367 }; 368 let result_query = plot_tag::find_many( 369 exec, 370 &IPlotTagFindMany { 371 filter: Some(filter), 372 }, 373 ); 374 let result = result_query?; 375 let mut tags = result 376 .results 377 .into_iter() 378 .map(|row| row.tag) 379 .collect::<Vec<_>>(); 380 tags.sort(); 381 tags.dedup(); 382 Ok(tags) 383 } 384 385 fn load_farm_members( 386 exec: &dyn SqlExecutor, 387 farm_id: &str, 388 ) -> Result<Vec<FarmMember>, RadrootsReplicaEventsError> { 389 let filter = IFarmMemberFieldsFilter { 390 id: None, 391 created_at: None, 392 updated_at: None, 393 farm_id: Some(farm_id.to_string()), 394 member_pubkey: None, 395 role: None, 396 }; 397 let result_query = farm_member::find_many( 398 exec, 399 &IFarmMemberFindMany { 400 filter: Some(filter), 401 }, 402 ); 403 let result = result_query?; 404 Ok(result.results) 405 } 406 407 fn role_pubkeys(members: &[FarmMember], role: &str) -> Vec<String> { 408 let mut values = members 409 .iter() 410 .filter(|member| member.role == role) 411 .map(|member| member.member_pubkey.clone()) 412 .collect::<Vec<_>>(); 413 values.sort(); 414 values.dedup(); 415 values 416 } 417 418 fn sorted_plot_ids(plots: &[Plot]) -> Vec<String> { 419 let mut ids = plots 420 .iter() 421 .map(|plot| plot.d_tag.clone()) 422 .collect::<Vec<_>>(); 423 ids.sort(); 424 ids.dedup(); 425 ids 426 } 427 428 fn load_plots( 429 exec: &dyn SqlExecutor, 430 farm_id: &str, 431 ) -> Result<Vec<Plot>, RadrootsReplicaEventsError> { 432 let filter = IPlotFieldsFilter { 433 id: None, 434 created_at: None, 435 updated_at: None, 436 d_tag: None, 437 farm_id: Some(farm_id.to_string()), 438 name: None, 439 about: None, 440 location_primary: None, 441 location_city: None, 442 location_region: None, 443 location_country: None, 444 }; 445 let result_query = plot::find_many( 446 exec, 447 &IPlotFindMany { 448 filter: Some(filter), 449 }, 450 ); 451 let result = result_query?; 452 let mut plots = result.results; 453 plots.sort_by(|a, b| a.d_tag.cmp(&b.d_tag)); 454 Ok(plots) 455 } 456 457 fn load_farm_location( 458 exec: &dyn SqlExecutor, 459 farm: &Farm, 460 ) -> Result<Option<FarmPublicLocation>, RadrootsReplicaEventsError> { 461 let Some(gcs) = load_gcs_location_for_farm(exec, &farm.id)? else { 462 return Ok(None); 463 }; 464 let Some(geohash) = gcs.geohash.get(..5).map(|value| value.to_ascii_lowercase()) else { 465 return Ok(None); 466 }; 467 if !is_public_geohash5(&geohash) { 468 return Ok(None); 469 } 470 let primary = farm 471 .location_primary 472 .as_ref() 473 .filter(|value| !value.trim().is_empty()) 474 .cloned() 475 .or_else(|| gcs.gc_name.clone()) 476 .or_else(|| gcs.label.clone()) 477 .unwrap_or_else(|| geohash.clone()); 478 let city = farm.location_city.clone().or_else(|| gcs.gc_name.clone()); 479 let region = farm 480 .location_region 481 .clone() 482 .or_else(|| gcs.gc_admin1_name.clone()); 483 let country = farm 484 .location_country 485 .clone() 486 .or_else(|| gcs.gc_country_name.clone()) 487 .or_else(|| gcs.gc_country_id.clone()); 488 if !has_textual_locality( 489 &primary, 490 city.as_deref(), 491 region.as_deref(), 492 country.as_deref(), 493 ) { 494 return Ok(None); 495 } 496 Ok(Some(FarmPublicLocation { 497 primary, 498 city, 499 region, 500 country, 501 geohash, 502 })) 503 } 504 505 fn load_plot_location( 506 exec: &dyn SqlExecutor, 507 plot: &Plot, 508 ) -> Result<Option<radroots_event::farm::plot::PlotLocation>, RadrootsReplicaEventsError> { 509 let location = load_gcs_location_for_plot(exec, &plot.id)?; 510 Ok( 511 location.map(|gcs| radroots_event::farm::plot::PlotLocation { 512 primary: plot.location_primary.clone(), 513 city: plot.location_city.clone(), 514 region: plot.location_region.clone(), 515 country: plot.location_country.clone(), 516 gcs, 517 }), 518 ) 519 } 520 521 fn load_gcs_location_for_farm( 522 exec: &dyn SqlExecutor, 523 farm_id: &str, 524 ) -> Result<Option<EventGcsLocation>, RadrootsReplicaEventsError> { 525 let primary = load_relation_by_role(exec, farm_id, ROLE_PRIMARY, RelationType::Farm)?; 526 match primary { 527 Some(gcs) => Ok(Some(gcs)), 528 None => load_relation_by_role(exec, farm_id, "", RelationType::Farm), 529 } 530 } 531 532 fn load_gcs_location_for_plot( 533 exec: &dyn SqlExecutor, 534 plot_id: &str, 535 ) -> Result<Option<EventGcsLocation>, RadrootsReplicaEventsError> { 536 let primary = load_relation_by_role(exec, plot_id, ROLE_PRIMARY, RelationType::Plot)?; 537 match primary { 538 Some(gcs) => Ok(Some(gcs)), 539 None => load_relation_by_role(exec, plot_id, "", RelationType::Plot), 540 } 541 } 542 543 enum RelationType { 544 Farm, 545 Plot, 546 } 547 548 fn load_relation_by_role( 549 exec: &dyn SqlExecutor, 550 id: &str, 551 role: &str, 552 relation: RelationType, 553 ) -> Result<Option<EventGcsLocation>, RadrootsReplicaEventsError> { 554 let mut rels = match relation { 555 RelationType::Farm => { 556 let filter = IFarmGcsLocationFieldsFilter { 557 id: None, 558 created_at: None, 559 updated_at: None, 560 farm_id: Some(id.to_string()), 561 gcs_location_id: None, 562 role: if role.is_empty() { 563 None 564 } else { 565 Some(role.to_string()) 566 }, 567 }; 568 let result_query = farm_gcs_location::find_many( 569 exec, 570 &IFarmGcsLocationFindMany { 571 filter: Some(filter), 572 }, 573 ); 574 let result = result_query?; 575 result 576 .results 577 .into_iter() 578 .map(RelationRow::Farm) 579 .collect::<Vec<_>>() 580 } 581 RelationType::Plot => { 582 let filter = IPlotGcsLocationFieldsFilter { 583 id: None, 584 created_at: None, 585 updated_at: None, 586 plot_id: Some(id.to_string()), 587 gcs_location_id: None, 588 role: if role.is_empty() { 589 None 590 } else { 591 Some(role.to_string()) 592 }, 593 }; 594 let result_query = plot_gcs_location::find_many( 595 exec, 596 &IPlotGcsLocationFindMany { 597 filter: Some(filter), 598 }, 599 ); 600 let result = result_query?; 601 result 602 .results 603 .into_iter() 604 .map(RelationRow::Plot) 605 .collect::<Vec<_>>() 606 } 607 }; 608 609 if rels.is_empty() { 610 return Ok(None); 611 } 612 613 rels.sort_by(compare_relation_rows); 614 let gcs_id = rels[0].gcs_location_id().to_string(); 615 let gcs_result = gcs_location::find_one( 616 exec, 617 &IGcsLocationFindOne::On(IGcsLocationFindOneArgs { 618 on: GcsLocationQueryBindValues::Id { id: gcs_id }, 619 }), 620 ); 621 let gcs = match gcs_result?.result { 622 Some(gcs) => gcs, 623 None => { 624 return Err(RadrootsReplicaEventsError::InvalidData( 625 "gcs_location not found".to_string(), 626 )); 627 } 628 }; 629 Ok(Some(gcs_location_to_event(&gcs)?)) 630 } 631 632 fn compare_relation_rows(a: &RelationRow, b: &RelationRow) -> core::cmp::Ordering { 633 let rank = location_role_rank(a.role()).cmp(&location_role_rank(b.role())); 634 rank.then_with(|| a.gcs_location_id().cmp(b.gcs_location_id())) 635 } 636 637 fn list_set_to_wire_parts( 638 list_set: &radroots_event::social::list_set::ListSet, 639 ) -> Result<Nip01EventWireParts, RadrootsReplicaEventsError> { 640 #[cfg(test)] 641 if failpoints::take_list_set_to_wire_error() { 642 return Err(RadrootsReplicaEventsError::InvalidData( 643 "list_set_to_wire".to_string(), 644 )); 645 } 646 Ok(list_set_encode::to_wire_parts_with_kind( 647 list_set, 648 KIND_LIST_SET_GENERIC, 649 )?) 650 } 651 652 enum RelationRow { 653 Farm(FarmGcsLocation), 654 Plot(PlotGcsLocation), 655 } 656 657 impl RelationRow { 658 fn gcs_location_id(&self) -> &str { 659 match self { 660 Self::Farm(row) => row.gcs_location_id.as_str(), 661 Self::Plot(row) => row.gcs_location_id.as_str(), 662 } 663 } 664 665 fn role(&self) -> &str { 666 match self { 667 Self::Farm(row) => row.role.as_str(), 668 Self::Plot(row) => row.role.as_str(), 669 } 670 } 671 } 672 673 fn location_role_rank(role: &str) -> u8 { 674 u8::from(role != ROLE_PRIMARY) 675 } 676 677 fn gcs_location_to_event( 678 gcs: &GcsLocation, 679 ) -> Result<EventGcsLocation, RadrootsReplicaEventsError> { 680 #[cfg(test)] 681 if failpoints::take_gcs_location_to_event_error() { 682 return Err(RadrootsReplicaEventsError::InvalidData( 683 "gcs_location_to_event".to_string(), 684 )); 685 } 686 let point = parse_point(&gcs.point, gcs.lat, gcs.lng); 687 let polygon = parse_polygon(&gcs.polygon, gcs.lat, gcs.lng); 688 Ok(EventGcsLocation { 689 lat: gcs.lat, 690 lng: gcs.lng, 691 geohash: gcs.geohash.clone(), 692 point, 693 polygon, 694 accuracy: gcs.accuracy, 695 altitude: gcs.altitude, 696 tag_0: gcs.tag_0.clone(), 697 label: gcs.label.clone(), 698 area: gcs.area, 699 elevation: gcs.elevation, 700 soil: gcs.soil.clone(), 701 climate: gcs.climate.clone(), 702 gc_id: gcs.gc_id.clone(), 703 gc_name: gcs.gc_name.clone(), 704 gc_admin1_id: gcs.gc_admin1_id.clone(), 705 gc_admin1_name: gcs.gc_admin1_name.clone(), 706 gc_country_id: gcs.gc_country_id.clone(), 707 gc_country_name: gcs.gc_country_name.clone(), 708 }) 709 } 710 711 fn parse_point(value: &str, lat: f64, lng: f64) -> GeoJsonPoint { 712 if !value.trim().is_empty() 713 && let Ok(parsed) = serde_json::from_str::<GeoJsonPoint>(value) 714 { 715 return parsed; 716 } 717 geojson_point_from_lat_lng(lat, lng) 718 } 719 720 fn parse_polygon(value: &str, lat: f64, lng: f64) -> GeoJsonPolygon { 721 if !value.trim().is_empty() 722 && let Ok(parsed) = serde_json::from_str::<GeoJsonPolygon>(value) 723 && !parsed.coordinates.is_empty() 724 && !parsed.coordinates[0].is_empty() 725 { 726 return parsed; 727 } 728 geojson_polygon_circle_wgs84(lat, lng, 100.0, 64) 729 } 730 731 fn load_member_claims( 732 exec: &dyn SqlExecutor, 733 farm_pubkey: &str, 734 ) -> Result<Vec<FarmMemberClaim>, RadrootsReplicaEventsError> { 735 let filter = IFarmMemberClaimFieldsFilter { 736 id: None, 737 created_at: None, 738 updated_at: None, 739 member_pubkey: None, 740 farm_pubkey: Some(farm_pubkey.to_string()), 741 }; 742 let result_query = farm_member_claim::find_many( 743 exec, 744 &IFarmMemberClaimFindMany { 745 filter: Some(filter), 746 }, 747 ); 748 let result = result_query?; 749 Ok(result.results) 750 } 751 752 fn load_member_claims_for_member( 753 exec: &dyn SqlExecutor, 754 member_pubkey: &str, 755 ) -> Result<Vec<FarmMemberClaim>, RadrootsReplicaEventsError> { 756 let filter = IFarmMemberClaimFieldsFilter { 757 id: None, 758 created_at: None, 759 updated_at: None, 760 member_pubkey: Some(member_pubkey.to_string()), 761 farm_pubkey: None, 762 }; 763 let result_query = farm_member_claim::find_many( 764 exec, 765 &IFarmMemberClaimFindMany { 766 filter: Some(filter), 767 }, 768 ); 769 let result = result_query?; 770 Ok(result.results) 771 } 772 773 fn parts_to_draft(author: &str, parts: Nip01EventWireParts) -> RadrootsReplicaEventDraft { 774 RadrootsReplicaEventDraft { 775 kind: parts.kind, 776 author: author.to_string(), 777 content: parts.content, 778 tags: parts.tags, 779 } 780 } 781 782 #[cfg(test)] 783 mod tests { 784 use super::*; 785 use radroots_replica_schema::farm::{IFarmFields, IFarmFieldsFilter, IFarmFindMany}; 786 use radroots_replica_schema::farm_gcs_location::{ 787 IFarmGcsLocationFields, IFarmGcsLocationFindMany, 788 }; 789 use radroots_replica_schema::farm_member::IFarmMemberFields; 790 use radroots_replica_schema::farm_member_claim::IFarmMemberClaimFields; 791 use radroots_replica_schema::farm_tag::IFarmTagFields; 792 use radroots_replica_schema::gcs_location::IGcsLocationFields; 793 use radroots_replica_schema::nostr_profile::INostrProfileFields; 794 use radroots_replica_schema::plot::{IPlotFields, IPlotFindMany}; 795 use radroots_replica_schema::plot_gcs_location::{ 796 IPlotGcsLocationFields, IPlotGcsLocationFindMany, 797 }; 798 use radroots_replica_schema::plot_tag::IPlotTagFields; 799 use radroots_replica_store::{ 800 farm, farm_gcs_location, farm_member, farm_member_claim, farm_tag, gcs_location, 801 migrations, nostr_profile, plot, plot_gcs_location, plot_tag, 802 }; 803 use radroots_sql_core::{ExecOutcome, SqlError, SqlExecutor, SqlxSqliteExecutor}; 804 805 struct ErrorExecutor; 806 807 impl SqlExecutor for ErrorExecutor { 808 fn exec(&self, _sql: &str, _params_json: &str) -> Result<ExecOutcome, SqlError> { 809 Err(SqlError::Internal) 810 } 811 812 fn query_raw(&self, _sql: &str, _params_json: &str) -> Result<String, SqlError> { 813 Err(SqlError::Internal) 814 } 815 816 fn begin(&self) -> Result<(), SqlError> { 817 Ok(()) 818 } 819 820 fn commit(&self) -> Result<(), SqlError> { 821 Ok(()) 822 } 823 824 fn rollback(&self) -> Result<(), SqlError> { 825 Ok(()) 826 } 827 } 828 829 struct QueryFailExecutor<'a> { 830 inner: &'a SqlxSqliteExecutor, 831 needle: &'static str, 832 err: SqlError, 833 } 834 835 impl SqlExecutor for QueryFailExecutor<'_> { 836 fn exec(&self, sql: &str, params_json: &str) -> Result<ExecOutcome, SqlError> { 837 self.inner.exec(sql, params_json) 838 } 839 840 fn query_raw(&self, sql: &str, params_json: &str) -> Result<String, SqlError> { 841 if sql.to_ascii_lowercase().contains(self.needle) { 842 return Err(self.err.clone()); 843 } 844 self.inner.query_raw(sql, params_json) 845 } 846 847 fn begin(&self) -> Result<(), SqlError> { 848 self.inner.begin() 849 } 850 851 fn commit(&self) -> Result<(), SqlError> { 852 self.inner.commit() 853 } 854 855 fn rollback(&self) -> Result<(), SqlError> { 856 self.inner.rollback() 857 } 858 } 859 860 struct DuplicateFarmSelectorExecutor<'a> { 861 inner: &'a SqlxSqliteExecutor, 862 duplicated_rows_json: String, 863 } 864 865 impl SqlExecutor for DuplicateFarmSelectorExecutor<'_> { 866 fn exec(&self, sql: &str, params_json: &str) -> Result<ExecOutcome, SqlError> { 867 self.inner.exec(sql, params_json) 868 } 869 870 fn query_raw(&self, sql: &str, params_json: &str) -> Result<String, SqlError> { 871 let _ = (sql, params_json); 872 Ok(self.duplicated_rows_json.clone()) 873 } 874 875 fn begin(&self) -> Result<(), SqlError> { 876 self.inner.begin() 877 } 878 879 fn commit(&self) -> Result<(), SqlError> { 880 self.inner.commit() 881 } 882 883 fn rollback(&self) -> Result<(), SqlError> { 884 self.inner.rollback() 885 } 886 } 887 888 fn seed(exec: &SqlxSqliteExecutor) -> (Farm, Plot, Plot) { 889 migrations::run_all_up(exec).expect("migrations"); 890 let farm = farm::create( 891 exec, 892 &IFarmFields { 893 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 894 pubkey: "f".repeat(64), 895 name: "farm".to_string(), 896 about: Some("about".to_string()), 897 website: Some("https://farm.example.com".to_string()), 898 picture: Some("https://farm.example.com/p.png".to_string()), 899 banner: Some("https://farm.example.com/b.png".to_string()), 900 location_primary: Some("primary".to_string()), 901 location_city: Some("city".to_string()), 902 location_region: Some("region".to_string()), 903 location_country: Some("country".to_string()), 904 }, 905 ) 906 .expect("farm") 907 .result; 908 909 let gcs_primary = gcs_location::create( 910 exec, 911 &IGcsLocationFields { 912 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 913 lat: 10.0, 914 lng: 20.0, 915 geohash: "9q8yy".to_string(), 916 point: "{\"type\":\"Point\",\"coordinates\":[20.0,10.0]}".to_string(), 917 polygon: 918 "{\"type\":\"Polygon\",\"coordinates\":[[[20.0,10.0],[20.1,10.1],[19.9,10.1],[20.0,10.0]]]}".to_string(), 919 accuracy: None, 920 altitude: None, 921 tag_0: None, 922 label: None, 923 area: None, 924 elevation: None, 925 soil: None, 926 climate: None, 927 gc_id: None, 928 gc_name: None, 929 gc_admin1_id: None, 930 gc_admin1_name: None, 931 gc_country_id: None, 932 gc_country_name: None, 933 }, 934 ) 935 .expect("gcs primary") 936 .result; 937 let gcs_secondary = gcs_location::create( 938 exec, 939 &IGcsLocationFields { 940 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 941 lat: 11.0, 942 lng: 21.0, 943 geohash: "9q8yv".to_string(), 944 point: "{".to_string(), 945 polygon: "{\"type\":\"Polygon\",\"coordinates\":[[]]}".to_string(), 946 accuracy: None, 947 altitude: None, 948 tag_0: None, 949 label: None, 950 area: None, 951 elevation: None, 952 soil: None, 953 climate: None, 954 gc_id: None, 955 gc_name: None, 956 gc_admin1_id: None, 957 gc_admin1_name: None, 958 gc_country_id: None, 959 gc_country_name: None, 960 }, 961 ) 962 .expect("gcs secondary") 963 .result; 964 965 let _ = farm_gcs_location::create( 966 exec, 967 &IFarmGcsLocationFields { 968 farm_id: farm.id.clone(), 969 gcs_location_id: gcs_secondary.id.clone(), 970 role: "".to_string(), 971 }, 972 ) 973 .expect("farm gcs secondary"); 974 let _ = farm_gcs_location::create( 975 exec, 976 &IFarmGcsLocationFields { 977 farm_id: farm.id.clone(), 978 gcs_location_id: gcs_primary.id.clone(), 979 role: "primary".to_string(), 980 }, 981 ) 982 .expect("farm gcs primary"); 983 984 let plot_primary = plot::create( 985 exec, 986 &IPlotFields { 987 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 988 farm_id: farm.id.clone(), 989 name: "plot-primary".to_string(), 990 about: Some("plot about".to_string()), 991 location_primary: Some("plot primary".to_string()), 992 location_city: Some("plot city".to_string()), 993 location_region: Some("plot region".to_string()), 994 location_country: Some("plot country".to_string()), 995 }, 996 ) 997 .expect("plot primary") 998 .result; 999 let plot_secondary = plot::create( 1000 exec, 1001 &IPlotFields { 1002 d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(), 1003 farm_id: farm.id.clone(), 1004 name: "plot-secondary".to_string(), 1005 about: Some("plot secondary about".to_string()), 1006 location_primary: Some("plot secondary primary".to_string()), 1007 location_city: None, 1008 location_region: None, 1009 location_country: None, 1010 }, 1011 ) 1012 .expect("plot secondary") 1013 .result; 1014 1015 let _ = plot_gcs_location::create( 1016 exec, 1017 &IPlotGcsLocationFields { 1018 plot_id: plot_primary.id.clone(), 1019 gcs_location_id: gcs_secondary.id.clone(), 1020 role: "secondary".to_string(), 1021 }, 1022 ) 1023 .expect("plot primary secondary relation"); 1024 let _ = plot_gcs_location::create( 1025 exec, 1026 &IPlotGcsLocationFields { 1027 plot_id: plot_primary.id.clone(), 1028 gcs_location_id: gcs_primary.id.clone(), 1029 role: "primary".to_string(), 1030 }, 1031 ) 1032 .expect("plot primary relation"); 1033 let _ = plot_gcs_location::create( 1034 exec, 1035 &IPlotGcsLocationFields { 1036 plot_id: plot_secondary.id.clone(), 1037 gcs_location_id: gcs_secondary.id.clone(), 1038 role: "secondary".to_string(), 1039 }, 1040 ) 1041 .expect("plot secondary relation"); 1042 1043 let _ = farm_tag::create( 1044 exec, 1045 &IFarmTagFields { 1046 farm_id: farm.id.clone(), 1047 tag: "coffee".to_string(), 1048 }, 1049 ) 1050 .expect("farm tag"); 1051 let _ = plot_tag::create( 1052 exec, 1053 &IPlotTagFields { 1054 plot_id: plot_primary.id.clone(), 1055 tag: "orchard".to_string(), 1056 }, 1057 ) 1058 .expect("plot tag"); 1059 1060 let _ = farm_member::create( 1061 exec, 1062 &IFarmMemberFields { 1063 farm_id: farm.id.clone(), 1064 member_pubkey: "6".repeat(64), 1065 role: "member".to_string(), 1066 }, 1067 ) 1068 .expect("member"); 1069 let _ = farm_member::create( 1070 exec, 1071 &IFarmMemberFields { 1072 farm_id: farm.id.clone(), 1073 member_pubkey: "8".repeat(64), 1074 role: "owner".to_string(), 1075 }, 1076 ) 1077 .expect("owner"); 1078 let _ = farm_member::create( 1079 exec, 1080 &IFarmMemberFields { 1081 farm_id: farm.id.clone(), 1082 member_pubkey: "e".repeat(64), 1083 role: "worker".to_string(), 1084 }, 1085 ) 1086 .expect("worker"); 1087 let _ = farm_member::create( 1088 exec, 1089 &IFarmMemberFields { 1090 farm_id: farm.id.clone(), 1091 member_pubkey: "1".repeat(64), 1092 role: "member".to_string(), 1093 }, 1094 ) 1095 .expect("member no profile"); 1096 1097 let _ = farm_member_claim::create( 1098 exec, 1099 &IFarmMemberClaimFields { 1100 member_pubkey: "6".repeat(64), 1101 farm_pubkey: farm.pubkey.clone(), 1102 }, 1103 ) 1104 .expect("claim member"); 1105 let _ = farm_member_claim::create( 1106 exec, 1107 &IFarmMemberClaimFields { 1108 member_pubkey: "1".repeat(64), 1109 farm_pubkey: farm.pubkey.clone(), 1110 }, 1111 ) 1112 .expect("claim member no profile"); 1113 1114 let _ = nostr_profile::create( 1115 exec, 1116 &INostrProfileFields { 1117 public_key: farm.pubkey.clone(), 1118 profile_type: "farm".to_string(), 1119 name: "farm profile".to_string(), 1120 display_name: None, 1121 about: None, 1122 website: None, 1123 picture: None, 1124 banner: None, 1125 nip05: None, 1126 lud06: None, 1127 lud16: None, 1128 }, 1129 ) 1130 .expect("farm profile"); 1131 let _ = nostr_profile::create( 1132 exec, 1133 &INostrProfileFields { 1134 public_key: "6".repeat(64), 1135 profile_type: "legacy".to_string(), 1136 name: "member profile".to_string(), 1137 display_name: Some("member".to_string()), 1138 about: Some("about".to_string()), 1139 website: Some("https://member.example.com".to_string()), 1140 picture: Some("https://member.example.com/p.png".to_string()), 1141 banner: Some("https://member.example.com/b.png".to_string()), 1142 nip05: Some("member@example.com".to_string()), 1143 lud06: Some("lud06".to_string()), 1144 lud16: Some("lud16".to_string()), 1145 }, 1146 ) 1147 .expect("member profile"); 1148 1149 (farm, plot_primary, plot_secondary) 1150 } 1151 1152 fn create_farm_record( 1153 exec: &SqlxSqliteExecutor, 1154 d_tag: &str, 1155 pubkey: &str, 1156 name: &str, 1157 ) -> Farm { 1158 farm::create( 1159 exec, 1160 &IFarmFields { 1161 d_tag: d_tag.to_string(), 1162 pubkey: pubkey.to_string(), 1163 name: name.to_string(), 1164 about: None, 1165 website: None, 1166 picture: None, 1167 banner: None, 1168 location_primary: None, 1169 location_city: None, 1170 location_region: None, 1171 location_country: None, 1172 }, 1173 ) 1174 .expect("farm") 1175 .result 1176 } 1177 1178 fn create_plot_record(exec: &SqlxSqliteExecutor, farm_id: &str, d_tag: &str, name: &str) { 1179 let _ = plot::create( 1180 exec, 1181 &IPlotFields { 1182 d_tag: d_tag.to_string(), 1183 farm_id: farm_id.to_string(), 1184 name: name.to_string(), 1185 about: None, 1186 location_primary: None, 1187 location_city: None, 1188 location_region: None, 1189 location_country: None, 1190 }, 1191 ) 1192 .expect("plot"); 1193 } 1194 1195 fn add_member_record( 1196 exec: &SqlxSqliteExecutor, 1197 farm_id: &str, 1198 member_pubkey: &str, 1199 role: &str, 1200 ) { 1201 let _ = farm_member::create( 1202 exec, 1203 &IFarmMemberFields { 1204 farm_id: farm_id.to_string(), 1205 member_pubkey: member_pubkey.to_string(), 1206 role: role.to_string(), 1207 }, 1208 ) 1209 .expect("member"); 1210 } 1211 1212 #[test] 1213 fn emit_paths_cover_private_and_public_helpers() { 1214 let exec = SqlxSqliteExecutor::open_memory().expect("db"); 1215 let (farm_row, plot_primary, plot_secondary) = seed(&exec); 1216 1217 let by_id = resolve_farm( 1218 &exec, 1219 &RadrootsReplicaFarmSelector { 1220 id: Some(farm_row.id.clone()), 1221 d_tag: None, 1222 pubkey: None, 1223 }, 1224 ) 1225 .expect("resolve by id"); 1226 assert_eq!(by_id.id, farm_row.id); 1227 1228 assert!( 1229 resolve_farm( 1230 &exec, 1231 &RadrootsReplicaFarmSelector { 1232 id: Some("00000000-0000-0000-0000-000000000000".to_string()), 1233 d_tag: None, 1234 pubkey: None, 1235 }, 1236 ) 1237 .is_err() 1238 ); 1239 assert!( 1240 radroots_replica_sync_all_with_options( 1241 &exec, 1242 &RadrootsReplicaFarmSelector { 1243 id: Some("00000000-0000-0000-0000-000000000000".to_string()), 1244 d_tag: None, 1245 pubkey: None, 1246 }, 1247 None, 1248 ) 1249 .is_err() 1250 ); 1251 assert!( 1252 resolve_farm( 1253 &exec, 1254 &RadrootsReplicaFarmSelector { 1255 id: None, 1256 d_tag: None, 1257 pubkey: None, 1258 }, 1259 ) 1260 .is_err() 1261 ); 1262 1263 assert!( 1264 farm::create( 1265 &exec, 1266 &IFarmFields { 1267 d_tag: farm_row.d_tag.clone(), 1268 pubkey: farm_row.pubkey.clone(), 1269 name: "duplicate".to_string(), 1270 about: None, 1271 website: None, 1272 picture: None, 1273 banner: None, 1274 location_primary: None, 1275 location_city: None, 1276 location_region: None, 1277 location_country: None, 1278 }, 1279 ) 1280 .is_err() 1281 ); 1282 let by_identity = resolve_farm( 1283 &exec, 1284 &RadrootsReplicaFarmSelector { 1285 id: None, 1286 d_tag: Some(farm_row.d_tag.clone()), 1287 pubkey: Some(farm_row.pubkey.clone()), 1288 }, 1289 ) 1290 .expect("resolve unique farm"); 1291 assert_eq!(by_identity.id, farm_row.id); 1292 1293 let tags = collect_farm_tags(&exec, &farm_row.id).expect("farm tags"); 1294 assert_eq!(tags, vec!["coffee".to_string()]); 1295 let plot_tags = collect_plot_tags(&exec, &plot_primary.id).expect("plot tags"); 1296 assert_eq!(plot_tags, vec!["orchard".to_string()]); 1297 1298 let members = load_farm_members(&exec, &farm_row.id).expect("members"); 1299 assert_eq!(role_pubkeys(&members, ROLE_MEMBER).len(), 2); 1300 assert_eq!(role_pubkeys(&members, ROLE_OWNER).len(), 1); 1301 assert_eq!(role_pubkeys(&members, ROLE_WORKER).len(), 1); 1302 let plots = load_plots(&exec, &farm_row.id).expect("plots"); 1303 assert_eq!(sorted_plot_ids(&plots).len(), 2); 1304 1305 let farm_location = load_farm_location(&exec, &farm_row).expect("farm location"); 1306 assert!(farm_location.is_some()); 1307 let plot_location_primary = load_plot_location(&exec, &plot_primary).expect("plot primary"); 1308 assert!(plot_location_primary.is_some()); 1309 let plot_location_secondary = 1310 load_plot_location(&exec, &plot_secondary).expect("plot secondary"); 1311 assert!(plot_location_secondary.is_some()); 1312 1313 assert!( 1314 load_relation_by_role(&exec, &farm_row.id, "primary", RelationType::Farm) 1315 .expect("farm primary") 1316 .is_some() 1317 ); 1318 assert!( 1319 load_relation_by_role(&exec, &farm_row.id, "", RelationType::Farm) 1320 .expect("farm fallback") 1321 .is_some() 1322 ); 1323 assert!( 1324 load_relation_by_role(&exec, &plot_secondary.id, "", RelationType::Plot) 1325 .expect("plot fallback") 1326 .is_some() 1327 ); 1328 1329 let mut farm_rel = 1330 farm_gcs_location::find_many(&exec, &IFarmGcsLocationFindMany { filter: None }) 1331 .expect("farm rels") 1332 .results; 1333 let mut plot_rel = 1334 plot_gcs_location::find_many(&exec, &IPlotGcsLocationFindMany { filter: None }) 1335 .expect("plot rels") 1336 .results; 1337 let farm_row_role = RelationRow::Farm(farm_rel.remove(0)).role().to_string(); 1338 let plot_row_role = RelationRow::Plot(plot_rel.remove(0)).role().to_string(); 1339 let _ = farm_row_role; 1340 let _ = plot_row_role; 1341 assert_eq!(location_role_rank(ROLE_PRIMARY), 0); 1342 assert_eq!(location_role_rank("secondary"), 1); 1343 1344 let point_valid = parse_point("{\"type\":\"Point\",\"coordinates\":[1.0,2.0]}", 3.0, 4.0); 1345 assert_eq!(point_valid.coordinates, [1.0, 2.0]); 1346 let point_invalid = parse_point("{", 3.0, 4.0); 1347 assert_eq!(point_invalid.coordinates, [4.0, 3.0]); 1348 let point_empty = parse_point("", 3.0, 4.0); 1349 assert_eq!(point_empty.coordinates, [4.0, 3.0]); 1350 1351 let polygon_valid = parse_polygon( 1352 "{\"type\":\"Polygon\",\"coordinates\":[[[1.0,2.0],[1.1,2.1],[1.0,2.0]]]}", 1353 3.0, 1354 4.0, 1355 ); 1356 assert!(!polygon_valid.coordinates[0].is_empty()); 1357 let polygon_empty_outer = 1358 parse_polygon("{\"type\":\"Polygon\",\"coordinates\":[]}", 3.0, 4.0); 1359 assert!(!polygon_empty_outer.coordinates[0].is_empty()); 1360 let polygon_empty_inner = 1361 parse_polygon("{\"type\":\"Polygon\",\"coordinates\":[[]]}", 3.0, 4.0); 1362 assert!(!polygon_empty_inner.coordinates[0].is_empty()); 1363 let polygon_invalid = parse_polygon("{", 3.0, 4.0); 1364 assert!(!polygon_invalid.coordinates[0].is_empty()); 1365 let polygon_blank = parse_polygon("", 3.0, 4.0); 1366 assert!(!polygon_blank.coordinates[0].is_empty()); 1367 1368 let claims = load_member_claims(&exec, &farm_row.pubkey).expect("claims"); 1369 assert!(!claims.is_empty()); 1370 let member_claims = 1371 load_member_claims_for_member(&exec, &"6".repeat(64)).expect("claims by member"); 1372 assert!(!member_claims.is_empty()); 1373 1374 let farm_event = radroots_replica_farm_event(&exec, &farm_row).expect("farm event"); 1375 assert_eq!(farm_event.kind, KIND_FARM); 1376 let plot_events = radroots_replica_plot_events(&exec, &farm_row).expect("plot events"); 1377 assert_eq!(plot_events.len(), 2); 1378 let list_sets = radroots_replica_list_set_events(&exec, &farm_row).expect("list sets"); 1379 assert_eq!(list_sets.len(), 4); 1380 let membership_claims = 1381 radroots_replica_membership_claim_events(&exec, &farm_row.pubkey).expect("membership"); 1382 assert!(!membership_claims.is_empty()); 1383 let bundle = radroots_replica_sync_all_with_options( 1384 &exec, 1385 &RadrootsReplicaFarmSelector { 1386 id: Some(farm_row.id.clone()), 1387 d_tag: None, 1388 pubkey: None, 1389 }, 1390 Some(&RadrootsReplicaSyncOptions { 1391 include_list_sets: Some(true), 1392 include_membership_claims: Some(true), 1393 }), 1394 ) 1395 .expect("sync all"); 1396 assert!(!bundle.events.is_empty()); 1397 1398 let _ = exec.exec("PRAGMA foreign_keys = OFF", "[]"); 1399 let _ = plot_gcs_location::create( 1400 &exec, 1401 &IPlotGcsLocationFields { 1402 plot_id: plot_secondary.id.clone(), 1403 gcs_location_id: "00000000-0000-0000-0000-000000000000".to_string(), 1404 role: "".to_string(), 1405 }, 1406 ); 1407 assert!(load_relation_by_role(&exec, &plot_secondary.id, "", RelationType::Plot).is_err()); 1408 1409 let by_pair = farm::find_many( 1410 &exec, 1411 &IFarmFindMany { 1412 filter: Some(IFarmFieldsFilter { 1413 id: None, 1414 created_at: None, 1415 updated_at: None, 1416 d_tag: Some("AAAAAAAAAAAAAAAAAAAAAA".to_string()), 1417 pubkey: Some("f".repeat(64)), 1418 name: None, 1419 about: None, 1420 website: None, 1421 picture: None, 1422 banner: None, 1423 location_primary: None, 1424 location_city: None, 1425 location_region: None, 1426 location_country: None, 1427 }), 1428 }, 1429 ) 1430 .expect("by pair"); 1431 assert!(!by_pair.results.is_empty()); 1432 1433 let plots_lookup = plot::find_many( 1434 &exec, 1435 &IPlotFindMany { 1436 filter: Some(IPlotFieldsFilter { 1437 id: None, 1438 created_at: None, 1439 updated_at: None, 1440 d_tag: None, 1441 farm_id: Some(farm_row.id), 1442 name: None, 1443 about: None, 1444 location_primary: None, 1445 location_city: None, 1446 location_region: None, 1447 location_country: None, 1448 }), 1449 }, 1450 ) 1451 .expect("plots lookup"); 1452 assert_eq!(plots_lookup.results.len(), 2); 1453 } 1454 1455 #[test] 1456 fn emit_option_toggles_and_empty_rows_cover_branches() { 1457 let exec = SqlxSqliteExecutor::open_memory().expect("db"); 1458 migrations::run_all_up(&exec).expect("migrations"); 1459 1460 let farm = farm::create( 1461 &exec, 1462 &IFarmFields { 1463 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 1464 pubkey: "b".repeat(64), 1465 name: "farm-empty".to_string(), 1466 about: None, 1467 website: None, 1468 picture: None, 1469 banner: None, 1470 location_primary: None, 1471 location_city: None, 1472 location_region: None, 1473 location_country: None, 1474 }, 1475 ) 1476 .expect("farm") 1477 .result; 1478 1479 let _plot = plot::create( 1480 &exec, 1481 &IPlotFields { 1482 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 1483 farm_id: farm.id.clone(), 1484 name: "plot-empty".to_string(), 1485 about: None, 1486 location_primary: None, 1487 location_city: None, 1488 location_region: None, 1489 location_country: None, 1490 }, 1491 ) 1492 .expect("plot"); 1493 1494 let selector = RadrootsReplicaFarmSelector { 1495 id: Some(farm.id.clone()), 1496 d_tag: None, 1497 pubkey: None, 1498 }; 1499 let bundle = radroots_replica_sync_all_with_options( 1500 &exec, 1501 &selector, 1502 Some(&RadrootsReplicaSyncOptions { 1503 include_list_sets: Some(false), 1504 include_membership_claims: Some(false), 1505 }), 1506 ) 1507 .expect("sync"); 1508 assert_eq!(bundle.events.len(), 2); 1509 1510 let farm_event = radroots_replica_farm_event(&exec, &farm).expect("farm event"); 1511 assert_eq!(farm_event.kind, KIND_FARM); 1512 let plot_events = radroots_replica_plot_events(&exec, &farm).expect("plot events"); 1513 assert_eq!(plot_events.len(), 1); 1514 1515 let claims = 1516 radroots_replica_membership_claim_events(&exec, &"3".repeat(64)).expect("empty claims"); 1517 assert!(claims.is_empty()); 1518 1519 let by_pair = resolve_farm( 1520 &exec, 1521 &RadrootsReplicaFarmSelector { 1522 id: None, 1523 d_tag: Some(farm.d_tag.clone()), 1524 pubkey: Some(farm.pubkey.clone()), 1525 }, 1526 ) 1527 .expect("resolve by pair"); 1528 assert_eq!(by_pair.id, farm.id); 1529 } 1530 1531 #[test] 1532 fn emit_query_error_paths_are_reported() { 1533 let exec = SqlxSqliteExecutor::open_memory().expect("db"); 1534 migrations::run_all_up(&exec).expect("migrations"); 1535 1536 let farm = Farm { 1537 id: "farm".to_string(), 1538 created_at: "now".to_string(), 1539 updated_at: "now".to_string(), 1540 d_tag: "d".to_string(), 1541 pubkey: "9".repeat(64), 1542 name: "farm".to_string(), 1543 about: None, 1544 website: None, 1545 picture: None, 1546 banner: None, 1547 location_primary: None, 1548 location_city: None, 1549 location_region: None, 1550 location_country: None, 1551 }; 1552 let plot = Plot { 1553 id: "plot".to_string(), 1554 created_at: "now".to_string(), 1555 updated_at: "now".to_string(), 1556 d_tag: "plot".to_string(), 1557 farm_id: farm.id.clone(), 1558 name: "plot".to_string(), 1559 about: None, 1560 location_primary: None, 1561 location_city: None, 1562 location_region: None, 1563 location_country: None, 1564 }; 1565 1566 let tags_fail = QueryFailExecutor { 1567 inner: &exec, 1568 needle: "farm_tag", 1569 err: SqlError::Internal, 1570 }; 1571 assert!(collect_farm_tags(&tags_fail, "id").is_err()); 1572 1573 let plot_tags_fail = QueryFailExecutor { 1574 inner: &exec, 1575 needle: "plot_tag", 1576 err: SqlError::Internal, 1577 }; 1578 assert!(collect_plot_tags(&plot_tags_fail, "id").is_err()); 1579 1580 let members_fail = QueryFailExecutor { 1581 inner: &exec, 1582 needle: "farm_member", 1583 err: SqlError::Internal, 1584 }; 1585 assert!(load_farm_members(&members_fail, "id").is_err()); 1586 1587 let plots_fail = QueryFailExecutor { 1588 inner: &exec, 1589 needle: "from plot", 1590 err: SqlError::Internal, 1591 }; 1592 assert!(load_plots(&plots_fail, "id").is_err()); 1593 1594 let farm_location_fail = QueryFailExecutor { 1595 inner: &exec, 1596 needle: "farm_gcs_location", 1597 err: SqlError::Internal, 1598 }; 1599 assert!(load_farm_location(&farm_location_fail, &farm).is_err()); 1600 1601 let plot_location_fail = QueryFailExecutor { 1602 inner: &exec, 1603 needle: "plot_gcs_location", 1604 err: SqlError::Internal, 1605 }; 1606 assert!(load_plot_location(&plot_location_fail, &plot).is_err()); 1607 1608 let claims_fail = QueryFailExecutor { 1609 inner: &exec, 1610 needle: "farm_member_claim", 1611 err: SqlError::Internal, 1612 }; 1613 assert!(load_member_claims(&claims_fail, "p").is_err()); 1614 assert!(load_member_claims_for_member(&claims_fail, "p").is_err()); 1615 } 1616 1617 #[test] 1618 fn load_farm_location_omits_string_only_public_locations() { 1619 let exec = SqlxSqliteExecutor::open_memory().expect("db"); 1620 migrations::run_all_up(&exec).expect("migrations"); 1621 let farm_row = farm::create( 1622 &exec, 1623 &IFarmFields { 1624 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 1625 pubkey: "f".repeat(64), 1626 name: "string-only farm".to_string(), 1627 about: None, 1628 website: None, 1629 picture: None, 1630 banner: None, 1631 location_primary: Some("San Francisco, CA".to_string()), 1632 location_city: Some("San Francisco".to_string()), 1633 location_region: Some("CA".to_string()), 1634 location_country: Some("US".to_string()), 1635 }, 1636 ) 1637 .expect("farm") 1638 .result; 1639 1640 let location = load_farm_location(&exec, &farm_row).expect("location query"); 1641 assert!(location.is_none()); 1642 } 1643 1644 #[test] 1645 fn emit_propagates_queryfail_and_builder_errors() { 1646 let exec = SqlxSqliteExecutor::open_memory().expect("db"); 1647 let (farm_row, _, _) = seed(&exec); 1648 let selector = RadrootsReplicaFarmSelector { 1649 id: Some(farm_row.id.clone()), 1650 d_tag: None, 1651 pubkey: None, 1652 }; 1653 1654 let farm_tag_fail = QueryFailExecutor { 1655 inner: &exec, 1656 needle: "farm_tag", 1657 err: SqlError::Internal, 1658 }; 1659 assert!(radroots_replica_farm_event(&farm_tag_fail, &farm_row).is_err()); 1660 1661 let plot_tag_fail = QueryFailExecutor { 1662 inner: &exec, 1663 needle: "plot_tag", 1664 err: SqlError::Internal, 1665 }; 1666 assert!(radroots_replica_plot_events(&plot_tag_fail, &farm_row).is_err()); 1667 1668 let list_set_fail = QueryFailExecutor { 1669 inner: &exec, 1670 needle: "farm_member", 1671 err: SqlError::Internal, 1672 }; 1673 assert!(radroots_replica_list_set_events(&list_set_fail, &farm_row).is_err()); 1674 1675 let claims_fail = QueryFailExecutor { 1676 inner: &exec, 1677 needle: "farm_member_claim", 1678 err: SqlError::Internal, 1679 }; 1680 assert!(radroots_replica_membership_claim_events(&claims_fail, &farm_row.pubkey).is_err()); 1681 1682 let sync_farm_fail = QueryFailExecutor { 1683 inner: &exec, 1684 needle: "farm_tag", 1685 err: SqlError::Internal, 1686 }; 1687 assert!( 1688 radroots_replica_sync_all_with_options( 1689 &sync_farm_fail, 1690 &selector, 1691 Some(&RadrootsReplicaSyncOptions { 1692 include_list_sets: Some(false), 1693 include_membership_claims: Some(false), 1694 }), 1695 ) 1696 .is_err() 1697 ); 1698 let sync_plot_fail = QueryFailExecutor { 1699 inner: &exec, 1700 needle: "plot_tag", 1701 err: SqlError::Internal, 1702 }; 1703 assert!( 1704 radroots_replica_sync_all_with_options( 1705 &sync_plot_fail, 1706 &selector, 1707 Some(&RadrootsReplicaSyncOptions { 1708 include_list_sets: Some(false), 1709 include_membership_claims: Some(false), 1710 }), 1711 ) 1712 .is_err() 1713 ); 1714 let sync_list_set_fail = QueryFailExecutor { 1715 inner: &exec, 1716 needle: "farm_member", 1717 err: SqlError::Internal, 1718 }; 1719 assert!( 1720 radroots_replica_sync_all_with_options( 1721 &sync_list_set_fail, 1722 &selector, 1723 Some(&RadrootsReplicaSyncOptions { 1724 include_list_sets: Some(true), 1725 include_membership_claims: Some(false), 1726 }), 1727 ) 1728 .is_err() 1729 ); 1730 let sync_claims_fail = QueryFailExecutor { 1731 inner: &exec, 1732 needle: "farm_member_claim", 1733 err: SqlError::Internal, 1734 }; 1735 assert!( 1736 radroots_replica_sync_all_with_options( 1737 &sync_claims_fail, 1738 &selector, 1739 Some(&RadrootsReplicaSyncOptions { 1740 include_list_sets: Some(false), 1741 include_membership_claims: Some(true), 1742 }), 1743 ) 1744 .is_err() 1745 ); 1746 1747 assert!(radroots_replica_farm_event(&exec, &farm_row).is_ok()); 1748 assert!(radroots_replica_plot_events(&exec, &farm_row).is_ok()); 1749 } 1750 1751 #[test] 1752 fn emit_additional_error_branches_are_reported() { 1753 let exec = SqlxSqliteExecutor::open_memory().expect("db"); 1754 let (farm_row, _, _) = seed(&exec); 1755 1756 let farm_location_fail = QueryFailExecutor { 1757 inner: &exec, 1758 needle: "farm_gcs_location", 1759 err: SqlError::Internal, 1760 }; 1761 assert!(radroots_replica_farm_event(&farm_location_fail, &farm_row).is_err()); 1762 let invalid_farm = Farm { 1763 id: farm_row.id.clone(), 1764 created_at: farm_row.created_at.clone(), 1765 updated_at: farm_row.updated_at.clone(), 1766 d_tag: "invalid".to_string(), 1767 pubkey: farm_row.pubkey.clone(), 1768 name: farm_row.name.clone(), 1769 about: farm_row.about.clone(), 1770 website: farm_row.website.clone(), 1771 picture: farm_row.picture.clone(), 1772 banner: farm_row.banner.clone(), 1773 location_primary: farm_row.location_primary.clone(), 1774 location_city: farm_row.location_city.clone(), 1775 location_region: farm_row.location_region.clone(), 1776 location_country: farm_row.location_country.clone(), 1777 }; 1778 assert!(radroots_replica_farm_event(&exec, &invalid_farm).is_err()); 1779 crate::canonical::failpoints::set_error(); 1780 assert!(radroots_replica_farm_event(&exec, &farm_row).is_err()); 1781 1782 let plots_fail = QueryFailExecutor { 1783 inner: &exec, 1784 needle: "from plot", 1785 err: SqlError::Internal, 1786 }; 1787 assert!(radroots_replica_plot_events(&plots_fail, &farm_row).is_err()); 1788 let plot_location_fail = QueryFailExecutor { 1789 inner: &exec, 1790 needle: "plot_gcs_location", 1791 err: SqlError::Internal, 1792 }; 1793 assert!(radroots_replica_plot_events(&plot_location_fail, &farm_row).is_err()); 1794 crate::canonical::failpoints::set_error(); 1795 assert!(radroots_replica_plot_events(&exec, &farm_row).is_err()); 1796 create_plot_record(&exec, &farm_row.id, "invalid", "plot-invalid"); 1797 assert!(radroots_replica_plot_events(&exec, &farm_row).is_err()); 1798 1799 let resolve_id_fail = QueryFailExecutor { 1800 inner: &exec, 1801 needle: "from farm", 1802 err: SqlError::Internal, 1803 }; 1804 assert!( 1805 resolve_farm( 1806 &resolve_id_fail, 1807 &RadrootsReplicaFarmSelector { 1808 id: Some(farm_row.id.clone()), 1809 d_tag: None, 1810 pubkey: None, 1811 } 1812 ) 1813 .is_err() 1814 ); 1815 let resolve_pair_fail = QueryFailExecutor { 1816 inner: &exec, 1817 needle: "from farm", 1818 err: SqlError::Internal, 1819 }; 1820 assert!( 1821 resolve_farm( 1822 &resolve_pair_fail, 1823 &RadrootsReplicaFarmSelector { 1824 id: None, 1825 d_tag: Some(farm_row.d_tag.clone()), 1826 pubkey: Some(farm_row.pubkey.clone()), 1827 } 1828 ) 1829 .is_err() 1830 ); 1831 1832 let gcs_query_fail = QueryFailExecutor { 1833 inner: &exec, 1834 needle: "from gcs_location", 1835 err: SqlError::Internal, 1836 }; 1837 assert!( 1838 load_relation_by_role( 1839 &gcs_query_fail, 1840 &farm_row.id, 1841 ROLE_PRIMARY, 1842 RelationType::Farm 1843 ) 1844 .is_err() 1845 ); 1846 super::failpoints::set_gcs_location_to_event_error(); 1847 assert!( 1848 load_relation_by_role(&exec, &farm_row.id, ROLE_PRIMARY, RelationType::Farm).is_err() 1849 ); 1850 1851 let list_plot_fail = QueryFailExecutor { 1852 inner: &exec, 1853 needle: "from plot", 1854 err: SqlError::Internal, 1855 }; 1856 assert!(radroots_replica_list_set_events(&list_plot_fail, &farm_row).is_err()); 1857 1858 let list_member_error_farm = create_farm_record( 1859 &exec, 1860 "AAAAAAAAAAAAAAAAAAAAAA", 1861 &"1".repeat(64), 1862 "list-member-error", 1863 ); 1864 add_member_record( 1865 &exec, 1866 &list_member_error_farm.id, 1867 &" ".repeat(64), 1868 ROLE_MEMBER, 1869 ); 1870 create_plot_record( 1871 &exec, 1872 &list_member_error_farm.id, 1873 "AAAAAAAAAAAAAAAAAAAAAQ", 1874 "plot-member-error", 1875 ); 1876 assert!(radroots_replica_list_set_events(&exec, &list_member_error_farm).is_err()); 1877 1878 let list_owner_error_farm = create_farm_record( 1879 &exec, 1880 "AAAAAAAAAAAAAAAAAAAAAA", 1881 &"2".repeat(64), 1882 "list-owner-error", 1883 ); 1884 add_member_record( 1885 &exec, 1886 &list_owner_error_farm.id, 1887 &"a".repeat(64), 1888 ROLE_MEMBER, 1889 ); 1890 add_member_record( 1891 &exec, 1892 &list_owner_error_farm.id, 1893 &" ".repeat(64), 1894 ROLE_OWNER, 1895 ); 1896 add_member_record( 1897 &exec, 1898 &list_owner_error_farm.id, 1899 &"b".repeat(64), 1900 ROLE_WORKER, 1901 ); 1902 create_plot_record( 1903 &exec, 1904 &list_owner_error_farm.id, 1905 "AAAAAAAAAAAAAAAAAAAAAQ", 1906 "plot-owner-error", 1907 ); 1908 assert!(radroots_replica_list_set_events(&exec, &list_owner_error_farm).is_err()); 1909 1910 let list_worker_error_farm = create_farm_record( 1911 &exec, 1912 "AAAAAAAAAAAAAAAAAAAAAA", 1913 &"3".repeat(64), 1914 "list-worker-error", 1915 ); 1916 add_member_record( 1917 &exec, 1918 &list_worker_error_farm.id, 1919 &"c".repeat(64), 1920 ROLE_MEMBER, 1921 ); 1922 add_member_record( 1923 &exec, 1924 &list_worker_error_farm.id, 1925 &"d".repeat(64), 1926 ROLE_OWNER, 1927 ); 1928 add_member_record( 1929 &exec, 1930 &list_worker_error_farm.id, 1931 &" ".repeat(64), 1932 ROLE_WORKER, 1933 ); 1934 create_plot_record( 1935 &exec, 1936 &list_worker_error_farm.id, 1937 "AAAAAAAAAAAAAAAAAAAAAQ", 1938 "plot-worker-error", 1939 ); 1940 assert!(radroots_replica_list_set_events(&exec, &list_worker_error_farm).is_err()); 1941 1942 let list_plot_error_farm = create_farm_record( 1943 &exec, 1944 "AAAAAAAAAAAAAAAAAAAAAA", 1945 &"4".repeat(64), 1946 "list-plot-error", 1947 ); 1948 add_member_record( 1949 &exec, 1950 &list_plot_error_farm.id, 1951 &"e".repeat(64), 1952 ROLE_MEMBER, 1953 ); 1954 add_member_record(&exec, &list_plot_error_farm.id, &"f".repeat(64), ROLE_OWNER); 1955 add_member_record( 1956 &exec, 1957 &list_plot_error_farm.id, 1958 &"7".repeat(64), 1959 ROLE_WORKER, 1960 ); 1961 create_plot_record(&exec, &list_plot_error_farm.id, "", "plot-list-error"); 1962 assert!(radroots_replica_list_set_events(&exec, &list_plot_error_farm).is_err()); 1963 1964 let clean_exec = SqlxSqliteExecutor::open_memory().expect("db clean"); 1965 let (clean_farm, _, _) = seed(&clean_exec); 1966 super::failpoints::set_list_set_to_wire_error(); 1967 assert!(radroots_replica_list_set_events(&clean_exec, &clean_farm).is_err()); 1968 1969 let invalid_list_set = radroots_event::social::list_set::ListSet { 1970 d_tag: String::new(), 1971 content: String::new(), 1972 entries: Vec::new(), 1973 title: None, 1974 description: None, 1975 image: None, 1976 }; 1977 assert!(list_set_to_wire_parts(&invalid_list_set).is_err()); 1978 1979 let claims_member_query_fail = QueryFailExecutor { 1980 inner: &exec, 1981 needle: "where member_pubkey", 1982 err: SqlError::Internal, 1983 }; 1984 assert!( 1985 radroots_replica_membership_claim_events(&claims_member_query_fail, &farm_row.pubkey) 1986 .is_err() 1987 ); 1988 1989 let _ = farm_member_claim::create( 1990 &exec, 1991 &IFarmMemberClaimFields { 1992 member_pubkey: "a".repeat(64), 1993 farm_pubkey: " ".repeat(64), 1994 }, 1995 ) 1996 .expect("empty-farm-pubkey claim"); 1997 assert!(radroots_replica_membership_claim_events(&exec, &" ".repeat(64)).is_err()); 1998 1999 super::failpoints::set_list_set_to_wire_error(); 2000 assert!(radroots_replica_membership_claim_events(&clean_exec, &clean_farm.pubkey).is_err()); 2001 } 2002 2003 #[test] 2004 fn emit_list_set_wire_error_paths_are_reported() { 2005 let exec = SqlxSqliteExecutor::open_memory().expect("db"); 2006 let (farm_row, _, _) = seed(&exec); 2007 2008 super::failpoints::set_list_set_to_wire_error(); 2009 assert!(radroots_replica_list_set_events(&exec, &farm_row).is_err()); 2010 2011 let invalid_list_set = radroots_event::social::list_set::ListSet { 2012 d_tag: String::new(), 2013 content: String::new(), 2014 entries: Vec::new(), 2015 title: None, 2016 description: None, 2017 image: None, 2018 }; 2019 assert!(list_set_to_wire_parts(&invalid_list_set).is_err()); 2020 } 2021 2022 #[test] 2023 fn emit_pass_through_executor_instantiation_paths_are_covered() { 2024 let exec = SqlxSqliteExecutor::open_memory().expect("db"); 2025 let (farm_row, _, plot_secondary) = seed(&exec); 2026 2027 let pass = QueryFailExecutor { 2028 inner: &exec, 2029 needle: "__never_match__", 2030 err: SqlError::Internal, 2031 }; 2032 2033 let selector = RadrootsReplicaFarmSelector { 2034 id: Some(farm_row.id.clone()), 2035 d_tag: None, 2036 pubkey: None, 2037 }; 2038 let request = RadrootsReplicaSyncRequest { 2039 farm: selector.clone(), 2040 options: None, 2041 }; 2042 let bundle = radroots_replica_sync_all(&pass, &request).expect("sync via pass executor"); 2043 assert!(!bundle.events.is_empty()); 2044 2045 let resolved_by_id = resolve_farm(&pass, &selector).expect("resolve by id"); 2046 assert_eq!(resolved_by_id.id, farm_row.id); 2047 let resolved_by_pair = resolve_farm( 2048 &pass, 2049 &RadrootsReplicaFarmSelector { 2050 id: None, 2051 d_tag: Some(farm_row.d_tag.clone()), 2052 pubkey: Some(farm_row.pubkey.clone()), 2053 }, 2054 ) 2055 .expect("resolve by pair"); 2056 assert_eq!(resolved_by_pair.id, farm_row.id); 2057 let duplicate_pair = DuplicateFarmSelectorExecutor { 2058 inner: &exec, 2059 duplicated_rows_json: { 2060 let farm_json = serde_json::to_string(&farm_row).expect("farm json"); 2061 format!("[{farm_json},{farm_json}]") 2062 }, 2063 }; 2064 duplicate_pair.begin().expect("duplicate begin"); 2065 duplicate_pair.rollback().expect("duplicate rollback"); 2066 duplicate_pair.begin().expect("duplicate begin"); 2067 duplicate_pair.commit().expect("duplicate commit"); 2068 duplicate_pair 2069 .exec("CREATE TABLE duplicate_probe (id INTEGER)", "[]") 2070 .expect("duplicate exec"); 2071 let duplicate_err = resolve_farm( 2072 &duplicate_pair, 2073 &RadrootsReplicaFarmSelector { 2074 id: None, 2075 d_tag: Some(farm_row.d_tag.clone()), 2076 pubkey: Some(farm_row.pubkey.clone()), 2077 }, 2078 ) 2079 .map(|_| ()) 2080 .unwrap_err(); 2081 assert!( 2082 duplicate_err 2083 .to_string() 2084 .contains("did not resolve to a single farm") 2085 ); 2086 assert!( 2087 resolve_farm( 2088 &pass, 2089 &RadrootsReplicaFarmSelector { 2090 id: Some("00000000-0000-0000-0000-000000000000".to_string()), 2091 d_tag: None, 2092 pubkey: None, 2093 }, 2094 ) 2095 .is_err() 2096 ); 2097 2098 assert!( 2099 load_relation_by_role(&pass, &farm_row.id, ROLE_PRIMARY, RelationType::Farm) 2100 .expect("farm primary relation") 2101 .is_some() 2102 ); 2103 assert!( 2104 load_relation_by_role(&pass, &farm_row.id, "", RelationType::Farm) 2105 .expect("farm fallback relation") 2106 .is_some() 2107 ); 2108 assert!( 2109 load_relation_by_role(&pass, &plot_secondary.id, "", RelationType::Plot) 2110 .expect("plot fallback relation") 2111 .is_some() 2112 ); 2113 } 2114 2115 #[test] 2116 fn emit_executor_trait_method_paths_are_covered() { 2117 let sqlite = SqlxSqliteExecutor::open_memory().expect("db"); 2118 migrations::run_all_up(&sqlite).expect("migrations"); 2119 2120 let err_exec = ErrorExecutor; 2121 assert!(err_exec.exec("SELECT 1", "[]").is_err()); 2122 assert!(err_exec.query_raw("SELECT 1", "[]").is_err()); 2123 assert!(err_exec.begin().is_ok()); 2124 assert!(err_exec.commit().is_ok()); 2125 assert!(err_exec.rollback().is_ok()); 2126 2127 let pass = QueryFailExecutor { 2128 inner: &sqlite, 2129 needle: "__never_match__", 2130 err: SqlError::Internal, 2131 }; 2132 assert!(pass.exec("PRAGMA foreign_keys = ON", "[]").is_ok()); 2133 assert!(pass.query_raw("SELECT 1", "[]").is_ok()); 2134 assert!(pass.begin().is_ok()); 2135 assert!(pass.commit().is_ok()); 2136 2137 let pass_rollback = QueryFailExecutor { 2138 inner: &sqlite, 2139 needle: "__never_match__", 2140 err: SqlError::Internal, 2141 }; 2142 assert!(pass_rollback.begin().is_ok()); 2143 assert!(pass_rollback.rollback().is_ok()); 2144 2145 let fail_query = QueryFailExecutor { 2146 inner: &sqlite, 2147 needle: "select 1", 2148 err: SqlError::Internal, 2149 }; 2150 assert!(fail_query.query_raw("SELECT 1", "[]").is_err()); 2151 } 2152 }