farm_workspace.rs (5400B)
1 #![cfg(feature = "json")] 2 3 use radroots_event::{ 4 envelope::kind::{KIND_FARM, KIND_FARM_FILE_METADATA}, 5 farm::FarmRef, 6 farm::crdt::KIND_FARM_CRDT_CHANGE, 7 farm::workspace::{ 8 FarmWorkspaceManifest, FarmWorkspaceMediaServer, FarmWorkspaceRelay, 9 FarmWorkspaceRelayMode, KIND_FARM_WORKSPACE_MANIFEST, 10 RADROOTS_FARM_WORKSPACE_PROTOCOL_VERSION, RADROOTS_FARM_WORKSPACE_SCHEMA, 11 }, 12 tag::name::{TAG_A, TAG_H, TAG_P}, 13 }; 14 use radroots_event_codec::{ 15 decode::EventParseError, decode::farm_workspace::farm_workspace_from_event, 16 encode::EventEncodeError, encode::farm_workspace::to_wire_parts, 17 }; 18 19 const D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAA"; 20 const FARM_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAQ"; 21 const OTHER_FARM_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAg"; 22 const GROUP_ID: &str = "field-group"; 23 24 #[test] 25 fn farm_workspace_decode_handles_optional_and_mismatch_edges() { 26 let manifest = sample_manifest(); 27 let parts = to_wire_parts(&manifest).unwrap(); 28 29 let mut mismatched_group = manifest.clone(); 30 mismatched_group.farm_group_id = "other-group".to_string(); 31 let content = serde_json::to_string(&mismatched_group).unwrap(); 32 assert!(matches!( 33 farm_workspace_from_event(parts.kind, &parts.tags, &content), 34 Err(EventParseError::InvalidTag(TAG_H)) 35 )); 36 37 let mut without_owner = parts.tags.clone(); 38 without_owner.retain(|tag| tag.first().map(String::as_str) != Some(TAG_P)); 39 let decoded = farm_workspace_from_event(parts.kind, &without_owner, &parts.content).unwrap(); 40 assert_eq!(decoded.owner_pubkey, "workspace_owner_pubkey"); 41 42 let mut without_farm_address = parts.tags.clone(); 43 without_farm_address.retain(|tag| tag.first().map(String::as_str) != Some(TAG_A)); 44 let decoded = 45 farm_workspace_from_event(parts.kind, &without_farm_address, &parts.content).unwrap(); 46 assert_eq!( 47 decoded.farm.as_ref().map(|farm| farm.d_tag.as_str()), 48 Some(FARM_D_TAG) 49 ); 50 51 let mut mismatched_farm_address = parts.tags.clone(); 52 replace_first_tag( 53 &mut mismatched_farm_address, 54 TAG_A, 55 vec![ 56 TAG_A.to_string(), 57 format!("{KIND_FARM}:farm_pubkey:{OTHER_FARM_D_TAG}"), 58 ], 59 ); 60 assert!(matches!( 61 farm_workspace_from_event(parts.kind, &mismatched_farm_address, &parts.content), 62 Err(EventParseError::InvalidTag(TAG_A)) 63 )); 64 65 let mut mismatched_farm_pubkey = parts.tags.clone(); 66 replace_first_tag( 67 &mut mismatched_farm_pubkey, 68 TAG_A, 69 vec![ 70 TAG_A.to_string(), 71 format!("{KIND_FARM}:other_farm:{FARM_D_TAG}"), 72 ], 73 ); 74 assert!(matches!( 75 farm_workspace_from_event(parts.kind, &mismatched_farm_pubkey, &parts.content), 76 Err(EventParseError::InvalidTag(TAG_A)) 77 )); 78 79 for supported_kinds in [ 80 vec![KIND_FARM_WORKSPACE_MANIFEST], 81 vec![KIND_FARM_CRDT_CHANGE], 82 ] { 83 let mut unsupported = manifest.clone(); 84 unsupported.supported_kinds = supported_kinds; 85 let content = serde_json::to_string(&unsupported).unwrap(); 86 assert!(matches!( 87 farm_workspace_from_event(parts.kind, &parts.tags, &content), 88 Err(EventParseError::InvalidJson("supported_kinds")) 89 )); 90 } 91 } 92 93 #[test] 94 fn farm_workspace_encode_rejects_schema_and_supported_kind_edges() { 95 let mut bad_schema = sample_manifest(); 96 bad_schema.schema = "radroots.farm.workspace.invalid".to_string(); 97 assert!(matches!( 98 to_wire_parts(&bad_schema), 99 Err(EventEncodeError::InvalidField("schema")) 100 )); 101 102 for supported_kinds in [ 103 vec![KIND_FARM_WORKSPACE_MANIFEST], 104 vec![KIND_FARM_CRDT_CHANGE], 105 ] { 106 let mut unsupported = sample_manifest(); 107 unsupported.supported_kinds = supported_kinds; 108 assert!(matches!( 109 to_wire_parts(&unsupported), 110 Err(EventEncodeError::InvalidField("supported_kinds")) 111 )); 112 } 113 } 114 115 fn sample_manifest() -> FarmWorkspaceManifest { 116 FarmWorkspaceManifest { 117 d_tag: D_TAG.to_string(), 118 schema: RADROOTS_FARM_WORKSPACE_SCHEMA.to_string(), 119 farm_group_id: GROUP_ID.to_string(), 120 name: "Small Regen Farm".to_string(), 121 owner_pubkey: "workspace_owner_pubkey".to_string(), 122 farm: Some(FarmRef { 123 pubkey: "farm_pubkey".to_string(), 124 d_tag: FARM_D_TAG.to_string(), 125 }), 126 relays: vec![FarmWorkspaceRelay { 127 url: "wss://relay.example.invalid/farm/field-group".to_string(), 128 mode: FarmWorkspaceRelayMode::ReadWrite, 129 }], 130 media_servers: vec![FarmWorkspaceMediaServer { 131 url: "https://media.example.invalid/farm/field-group".to_string(), 132 service: "RadrootsPrivateMedia".to_string(), 133 }], 134 supported_kinds: vec![ 135 KIND_FARM_CRDT_CHANGE, 136 KIND_FARM_WORKSPACE_MANIFEST, 137 KIND_FARM_FILE_METADATA, 138 ], 139 protocol_version: RADROOTS_FARM_WORKSPACE_PROTOCOL_VERSION.to_string(), 140 created_at_ms: 1_780_000_000_000, 141 updated_at_ms: None, 142 } 143 } 144 145 fn replace_first_tag(tags: &mut [Vec<String>], name: &str, replacement: Vec<String>) { 146 let tag = tags 147 .iter_mut() 148 .find(|tag| tag.first().map(String::as_str) == Some(name)) 149 .expect("tag"); 150 *tag = replacement; 151 }