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