@@ -393,16 +393,15 @@ pub async fn cmd_get_messages(
393393}
394394
395395pub fn resolve_thread_target (
396- channel_id : & str ,
396+ expected_channel_id : Uuid ,
397397 event_id : & str ,
398398 expected_root_id : Option < & str > ,
399399 selected_event : & serde_json:: Value ,
400400) -> Result < String , CliError > {
401- let expected_channel_id = parse_uuid ( channel_id) ?;
402401 let actual_channel_id = channel_id_from_event ( event_id, selected_event) ?;
403402 if actual_channel_id != expected_channel_id {
404403 return Err ( CliError :: Usage ( format ! (
405- "event {event_id} does not belong to channel {channel_id }"
404+ "event {event_id} does not belong to channel {expected_channel_id }"
406405 ) ) ) ;
407406 }
408407 let root_event_id = thread_ref_from_event ( event_id, selected_event) ?
@@ -425,10 +424,15 @@ pub async fn cmd_get_thread(
425424 depth_limit : Option < u32 > ,
426425 format : & crate :: OutputFormat ,
427426) -> Result < ( ) , CliError > {
427+ let expected_channel_id = parse_uuid ( channel_id) ?;
428428 validate_hex64 ( event_id) ?;
429429 let selected_event = fetch_event ( client, event_id) . await ?;
430- let root_event_id =
431- resolve_thread_target ( channel_id, event_id, expected_root_id, & selected_event) ?;
430+ let root_event_id = resolve_thread_target (
431+ expected_channel_id,
432+ event_id,
433+ expected_root_id,
434+ & selected_event,
435+ ) ?;
432436 let limit = limit. unwrap_or ( 100 ) . min ( 500 ) ;
433437
434438 let mut reply_filter = serde_json:: json!( {
@@ -1050,13 +1054,15 @@ pub async fn dispatch(
10501054#[ cfg( test) ]
10511055mod tests {
10521056 use super :: {
1053- channel_id_from_event, event_mention_pubkeys, find_root_from_tags, match_profiles_by_name,
1054- merge_message_mentions, missing_members, normalize_explicit_mentions, parse_member_pubkeys,
1055- resolve_names_to_pubkeys, resolve_thread_target, thread_ref_from_event,
1057+ channel_id_from_event, cmd_get_thread, event_mention_pubkeys, find_root_from_tags,
1058+ match_profiles_by_name, merge_message_mentions, missing_members,
1059+ normalize_explicit_mentions, parse_member_pubkeys, resolve_names_to_pubkeys,
1060+ resolve_thread_target, thread_ref_from_event, BuzzClient , CliError , Uuid ,
10561061 } ;
10571062 use buzz_sdk:: mentions:: {
10581063 extract_at_mentions_with_known, extract_at_names, match_names_to_profiles, MentionProfile ,
10591064 } ;
1065+ use nostr:: Keys ;
10601066 use serde_json:: json;
10611067
10621068 const ID_A : & str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ;
@@ -1069,6 +1075,26 @@ mod tests {
10691075 const PK_VALID_B : & str = "c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05" ;
10701076 const PK_VALID_C : & str = "f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68" ;
10711077
1078+ #[ tokio:: test]
1079+ async fn malformed_channel_is_rejected_before_thread_fetch ( ) {
1080+ let client =
1081+ BuzzClient :: new ( "http://127.0.0.1:1" . into ( ) , Keys :: generate ( ) , None , None ) . unwrap ( ) ;
1082+ let error = cmd_get_thread (
1083+ & client,
1084+ "not-a-uuid" ,
1085+ ID_A ,
1086+ None ,
1087+ None ,
1088+ None ,
1089+ & crate :: OutputFormat :: Json ,
1090+ )
1091+ . await
1092+ . unwrap_err ( ) ;
1093+
1094+ assert ! ( matches!( error, CliError :: Usage ( _) ) ) ;
1095+ assert ! ( error. to_string( ) . contains( "invalid UUID" ) ) ;
1096+ }
1097+
10721098 #[ test]
10731099 fn selected_event_derives_authoritative_channel_and_root ( ) {
10741100 let channel = "123e4567-e89b-12d3-a456-426614174000" ;
@@ -1109,10 +1135,28 @@ mod tests {
11091135 "tags" : [ [ "h" , channel] , [ "e" , ID_A , "" , "root" ] ]
11101136 } ) ;
11111137
1112- assert ! ( resolve_thread_target( other_channel, ID_B , Some ( ID_A ) , & selected) . is_err( ) ) ;
1113- assert ! ( resolve_thread_target( channel, ID_B , Some ( ID_B ) , & selected) . is_err( ) ) ;
1138+ assert ! ( resolve_thread_target(
1139+ Uuid :: parse_str( other_channel) . unwrap( ) ,
1140+ ID_B ,
1141+ Some ( ID_A ) ,
1142+ & selected,
1143+ )
1144+ . is_err( ) ) ;
1145+ assert ! ( resolve_thread_target(
1146+ Uuid :: parse_str( channel) . unwrap( ) ,
1147+ ID_B ,
1148+ Some ( ID_B ) ,
1149+ & selected,
1150+ )
1151+ . is_err( ) ) ;
11141152 assert_eq ! (
1115- resolve_thread_target( channel, ID_B , Some ( ID_A ) , & selected) . unwrap( ) ,
1153+ resolve_thread_target(
1154+ Uuid :: parse_str( channel) . unwrap( ) ,
1155+ ID_B ,
1156+ Some ( ID_A ) ,
1157+ & selected,
1158+ )
1159+ . unwrap( ) ,
11161160 ID_A
11171161 ) ;
11181162 }
0 commit comments