@@ -2,7 +2,9 @@ use crate::{
22 doublezerocommand:: CliCommand ,
33 poll_for_activation:: poll_for_multicastgroup_activated,
44 requirements:: { CHECK_BALANCE , CHECK_ID_JSON } ,
5- validators:: { validate_code, validate_parse_bandwidth, validate_pubkey_or_code} ,
5+ validators:: {
6+ validate_code, validate_parse_bandwidth, validate_pubkey, validate_pubkey_or_code,
7+ } ,
68} ;
79use clap:: Args ;
810use doublezero_sdk:: commands:: multicastgroup:: {
@@ -30,6 +32,9 @@ pub struct UpdateMulticastGroupCliCommand {
3032 /// Updated subscriber count
3133 #[ arg( long) ]
3234 pub subscriber_count : Option < u32 > ,
35+ /// Updated owner pubkey for the multicast group
36+ #[ arg( long, value_parser = validate_pubkey) ]
37+ pub owner : Option < String > ,
3338 /// Wait for the multicast group to be activated
3439 #[ arg( short, long, default_value_t = false ) ]
3540 pub wait : bool ,
@@ -51,6 +56,13 @@ impl UpdateMulticastGroupCliCommand {
5156 max_bandwidth : self . max_bandwidth ,
5257 publisher_count : self . publisher_count ,
5358 subscriber_count : self . subscriber_count ,
59+ owner : self . owner . as_deref ( ) . map ( |s| {
60+ if s. eq_ignore_ascii_case ( "me" ) {
61+ client. get_payer ( )
62+ } else {
63+ s. parse ( ) . unwrap ( )
64+ }
65+ } ) ,
5466 } ) ?;
5567 writeln ! ( out, "Signature: {signature}" , ) ?;
5668
@@ -80,11 +92,35 @@ mod tests {
8092 use mockall:: predicate;
8193 use solana_sdk:: { pubkey:: Pubkey , signature:: Signature } ;
8294
95+ const SIGNATURE_BYTES : [ u8 ; 64 ] = [
96+ 120 , 138 , 162 , 185 , 59 , 209 , 241 , 157 , 71 , 157 , 74 , 131 , 4 , 87 , 54 , 28 , 38 , 180 , 222 , 82 ,
97+ 64 , 62 , 61 , 62 , 22 , 46 , 17 , 203 , 187 , 136 , 62 , 43 , 11 , 38 , 235 , 17 , 239 , 82 , 240 , 139 , 130 ,
98+ 217 , 227 , 214 , 9 , 242 , 141 , 223 , 94 , 29 , 184 , 110 , 62 , 32 , 87 , 137 , 63 , 139 , 100 , 221 , 20 ,
99+ 137 , 4 , 5 ,
100+ ] ;
101+
102+ const EXPECTED_SIGNATURE_STR : & str = "Signature: 3QnHBSdd4doEF6FgpLCejqEw42UQjfvNhQJwoYDSpoBszpCCqVft4cGoneDCnZ6Ez3ujzavzUu85u6F79WtLhcsv\n " ;
103+
104+ fn make_multicastgroup ( pda_pubkey : Pubkey ) -> MulticastGroup {
105+ MulticastGroup {
106+ account_type : AccountType :: MulticastGroup ,
107+ index : 1 ,
108+ bump_seed : 255 ,
109+ code : "test" . to_string ( ) ,
110+ tenant_pk : Pubkey :: new_unique ( ) ,
111+ multicast_ip : [ 10 , 0 , 0 , 1 ] . into ( ) ,
112+ max_bandwidth : 1000000000 ,
113+ status : MulticastGroupStatus :: Activated ,
114+ owner : pda_pubkey,
115+ publisher_count : 5 ,
116+ subscriber_count : 10 ,
117+ }
118+ }
119+
83120 #[ test]
84121 fn test_cli_multicastgroup_update_bandwidth_parsing ( ) {
85122 use clap:: Parser ;
86123
87- // Define a test CLI structure to parse arguments
88124 #[ derive( Parser , Debug ) ]
89125 struct TestCli {
90126 #[ command( subcommand) ]
@@ -96,14 +132,13 @@ mod tests {
96132 Update ( UpdateMulticastGroupCliCommand ) ,
97133 }
98134
99- // Test various bandwidth formats
100135 let test_cases = vec ! [
101136 ( "1Gbps" , 1_000_000_000u64 ) ,
102137 ( "100Mbps" , 100_000_000u64 ) ,
103138 ( "500Kbps" , 500_000u64 ) ,
104139 ( "1000bps" , 1_000u64 ) ,
105- ( "10gbps" , 10_000_000_000u64 ) , // lowercase
106- ( "2.5Gbps" , 2_500_000_000u64 ) , // decimal
140+ ( "10gbps" , 10_000_000_000u64 ) ,
141+ ( "2.5Gbps" , 2_500_000_000u64 ) ,
107142 ] ;
108143
109144 for ( input, expected) in test_cases {
@@ -139,7 +174,6 @@ mod tests {
139174 }
140175 }
141176
142- // Test invalid bandwidth formats
143177 let invalid_cases = vec ! [ "invalid" , "abc" , "Gbps" , "" ] ;
144178
145179 for input in invalid_cases {
@@ -161,31 +195,68 @@ mod tests {
161195 }
162196 }
163197
198+ #[ test]
199+ fn test_cli_multicastgroup_update_owner_parsing ( ) {
200+ use clap:: Parser ;
201+
202+ #[ derive( Parser , Debug ) ]
203+ struct TestCli {
204+ #[ command( subcommand) ]
205+ command : TestCommand ,
206+ }
207+
208+ #[ derive( clap:: Subcommand , Debug ) ]
209+ enum TestCommand {
210+ Update ( UpdateMulticastGroupCliCommand ) ,
211+ }
212+
213+ let valid_pubkey = Pubkey :: new_unique ( ) . to_string ( ) ;
214+
215+ let valid_cases = vec ! [ "me" , valid_pubkey. as_str( ) ] ;
216+ for input in valid_cases {
217+ let args = vec ! [
218+ "test" ,
219+ "update" ,
220+ "--pubkey" ,
221+ "test-pubkey" ,
222+ "--owner" ,
223+ input,
224+ ] ;
225+ let result = TestCli :: try_parse_from ( args) ;
226+ assert ! (
227+ result. is_ok( ) ,
228+ "Should have accepted owner '{}': {:?}" ,
229+ input,
230+ result. err( )
231+ ) ;
232+ }
233+
234+ let invalid_cases = vec ! [ "not_a_pubkey" , "invalid key!" ] ;
235+ for input in invalid_cases {
236+ let args = vec ! [
237+ "test" ,
238+ "update" ,
239+ "--pubkey" ,
240+ "test-pubkey" ,
241+ "--owner" ,
242+ input,
243+ ] ;
244+ let result = TestCli :: try_parse_from ( args) ;
245+ assert ! (
246+ result. is_err( ) ,
247+ "Should have rejected invalid owner '{}'" ,
248+ input
249+ ) ;
250+ }
251+ }
252+
164253 #[ test]
165254 fn test_cli_multicastgroup_update ( ) {
166255 let mut client = create_test_client ( ) ;
167256
168257 let ( pda_pubkey, _bump_seed) = get_multicastgroup_pda ( & client. get_program_id ( ) , 1 ) ;
169- let signature = Signature :: from ( [
170- 120 , 138 , 162 , 185 , 59 , 209 , 241 , 157 , 71 , 157 , 74 , 131 , 4 , 87 , 54 , 28 , 38 , 180 , 222 ,
171- 82 , 64 , 62 , 61 , 62 , 22 , 46 , 17 , 203 , 187 , 136 , 62 , 43 , 11 , 38 , 235 , 17 , 239 , 82 , 240 ,
172- 139 , 130 , 217 , 227 , 214 , 9 , 242 , 141 , 223 , 94 , 29 , 184 , 110 , 62 , 32 , 87 , 137 , 63 , 139 ,
173- 100 , 221 , 20 , 137 , 4 , 5 ,
174- ] ) ;
175-
176- let multicastgroup = MulticastGroup {
177- account_type : AccountType :: MulticastGroup ,
178- index : 1 ,
179- bump_seed : 255 ,
180- code : "test" . to_string ( ) ,
181- tenant_pk : Pubkey :: new_unique ( ) ,
182- multicast_ip : [ 10 , 0 , 0 , 1 ] . into ( ) ,
183- max_bandwidth : 1000000000 ,
184- status : MulticastGroupStatus :: Activated ,
185- owner : pda_pubkey,
186- publisher_count : 5 ,
187- subscriber_count : 10 ,
188- } ;
258+ let signature = Signature :: from ( SIGNATURE_BYTES ) ;
259+ let multicastgroup = make_multicastgroup ( pda_pubkey) ;
189260
190261 client
191262 . expect_check_requirements ( )
@@ -206,10 +277,10 @@ mod tests {
206277 max_bandwidth : Some ( 1000000000 ) ,
207278 publisher_count : Some ( 5 ) ,
208279 subscriber_count : Some ( 10 ) ,
280+ owner : None ,
209281 } ) )
210282 . returning ( move |_| Ok ( signature) ) ;
211283
212- /*****************************************************************************************************/
213284 let mut output = Vec :: new ( ) ;
214285 let res = UpdateMulticastGroupCliCommand {
215286 pubkey : pda_pubkey. to_string ( ) ,
@@ -218,13 +289,108 @@ mod tests {
218289 max_bandwidth : Some ( 1000000000 ) ,
219290 publisher_count : Some ( 5 ) ,
220291 subscriber_count : Some ( 10 ) ,
292+ owner : None ,
293+ wait : false ,
294+ }
295+ . execute ( & client, & mut output) ;
296+ assert ! ( res. is_ok( ) ) ;
297+ assert_eq ! ( String :: from_utf8( output) . unwrap( ) , EXPECTED_SIGNATURE_STR ) ;
298+ }
299+
300+ #[ test]
301+ fn test_cli_multicastgroup_update_with_explicit_owner ( ) {
302+ let mut client = create_test_client ( ) ;
303+
304+ let ( pda_pubkey, _bump_seed) = get_multicastgroup_pda ( & client. get_program_id ( ) , 1 ) ;
305+ let explicit_owner = Pubkey :: new_unique ( ) ;
306+ let signature = Signature :: from ( SIGNATURE_BYTES ) ;
307+ let multicastgroup = make_multicastgroup ( pda_pubkey) ;
308+
309+ client
310+ . expect_check_requirements ( )
311+ . with ( predicate:: eq ( CHECK_ID_JSON | CHECK_BALANCE ) )
312+ . returning ( |_| Ok ( ( ) ) ) ;
313+ client
314+ . expect_get_multicastgroup ( )
315+ . with ( predicate:: eq ( GetMulticastGroupCommand {
316+ pubkey_or_code : pda_pubkey. to_string ( ) ,
317+ } ) )
318+ . returning ( move |_| Ok ( ( pda_pubkey, multicastgroup. clone ( ) ) ) ) ;
319+ client
320+ . expect_update_multicastgroup ( )
321+ . with ( predicate:: eq ( UpdateMulticastGroupCommand {
322+ pubkey : pda_pubkey,
323+ code : None ,
324+ multicast_ip : None ,
325+ max_bandwidth : None ,
326+ publisher_count : None ,
327+ subscriber_count : None ,
328+ owner : Some ( explicit_owner) ,
329+ } ) )
330+ . returning ( move |_| Ok ( signature) ) ;
331+
332+ let mut output = Vec :: new ( ) ;
333+ let res = UpdateMulticastGroupCliCommand {
334+ pubkey : pda_pubkey. to_string ( ) ,
335+ code : None ,
336+ multicast_ip : None ,
337+ max_bandwidth : None ,
338+ publisher_count : None ,
339+ subscriber_count : None ,
340+ owner : Some ( explicit_owner. to_string ( ) ) ,
341+ wait : false ,
342+ }
343+ . execute ( & client, & mut output) ;
344+ assert ! ( res. is_ok( ) ) ;
345+ assert_eq ! ( String :: from_utf8( output) . unwrap( ) , EXPECTED_SIGNATURE_STR ) ;
346+ }
347+
348+ #[ test]
349+ fn test_cli_multicastgroup_update_owner_me ( ) {
350+ let mut client = create_test_client ( ) ;
351+
352+ let ( pda_pubkey, _bump_seed) = get_multicastgroup_pda ( & client. get_program_id ( ) , 1 ) ;
353+ // The payer configured in create_test_client()
354+ let payer = Pubkey :: from_str_const ( "DDddB7bhR9azxLAUEH7ZVtW168wRdreiDKhi4McDfKZt" ) ;
355+ let signature = Signature :: from ( SIGNATURE_BYTES ) ;
356+ let multicastgroup = make_multicastgroup ( pda_pubkey) ;
357+
358+ client
359+ . expect_check_requirements ( )
360+ . with ( predicate:: eq ( CHECK_ID_JSON | CHECK_BALANCE ) )
361+ . returning ( |_| Ok ( ( ) ) ) ;
362+ client
363+ . expect_get_multicastgroup ( )
364+ . with ( predicate:: eq ( GetMulticastGroupCommand {
365+ pubkey_or_code : pda_pubkey. to_string ( ) ,
366+ } ) )
367+ . returning ( move |_| Ok ( ( pda_pubkey, multicastgroup. clone ( ) ) ) ) ;
368+ client
369+ . expect_update_multicastgroup ( )
370+ . with ( predicate:: eq ( UpdateMulticastGroupCommand {
371+ pubkey : pda_pubkey,
372+ code : None ,
373+ multicast_ip : None ,
374+ max_bandwidth : None ,
375+ publisher_count : None ,
376+ subscriber_count : None ,
377+ owner : Some ( payer) ,
378+ } ) )
379+ . returning ( move |_| Ok ( signature) ) ;
380+
381+ let mut output = Vec :: new ( ) ;
382+ let res = UpdateMulticastGroupCliCommand {
383+ pubkey : pda_pubkey. to_string ( ) ,
384+ code : None ,
385+ multicast_ip : None ,
386+ max_bandwidth : None ,
387+ publisher_count : None ,
388+ subscriber_count : None ,
389+ owner : Some ( "me" . to_string ( ) ) ,
221390 wait : false ,
222391 }
223392 . execute ( & client, & mut output) ;
224393 assert ! ( res. is_ok( ) ) ;
225- let output_str = String :: from_utf8 ( output) . unwrap ( ) ;
226- assert_eq ! (
227- output_str, "Signature: 3QnHBSdd4doEF6FgpLCejqEw42UQjfvNhQJwoYDSpoBszpCCqVft4cGoneDCnZ6Ez3ujzavzUu85u6F79WtLhcsv\n "
228- ) ;
394+ assert_eq ! ( String :: from_utf8( output) . unwrap( ) , EXPECTED_SIGNATURE_STR ) ;
229395 }
230396}
0 commit comments