@@ -2,6 +2,7 @@ use std::io::Write;
22use std:: path:: PathBuf ;
33use std:: sync:: { Mutex , MutexGuard } ;
44
5+ use assert_matches:: assert_matches;
56use clap:: Parser ;
67use rstest:: rstest;
78use tempfile:: NamedTempFile ;
@@ -296,3 +297,130 @@ fn env_var_sets_bouncer_config_override() {
296297
297298 assert_eq ! ( args. bouncer_config_override, Some ( PathBuf :: from( "/tmp/bouncer.json" ) ) ) ;
298299}
300+
301+ #[ test]
302+ fn test_missing_rpc_url_rejected ( ) {
303+ let mut args = base_args ( ) ;
304+ args. rpc_url = None ; // No CLI arg, and no config file, so default empty rpc_node_url is used.
305+
306+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
307+
308+ assert_matches ! ( error, ConfigError :: MissingRequiredField ( _) ) ;
309+ }
310+
311+ #[ test]
312+ fn test_max_concurrent_requests_zero_rejected ( ) {
313+ let mut args = base_args ( ) ;
314+ args. max_concurrent_requests = Some ( 0 ) ;
315+
316+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
317+
318+ assert_matches ! ( error, ConfigError :: InvalidArgument ( _) ) ;
319+ }
320+
321+ #[ test]
322+ fn test_max_connections_zero_rejected ( ) {
323+ let mut args = base_args ( ) ;
324+ args. max_connections = Some ( 0 ) ;
325+
326+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
327+
328+ assert_matches ! ( error, ConfigError :: InvalidArgument ( _) ) ;
329+ }
330+
331+ #[ test]
332+ fn test_no_cors_with_cors_allow_origin_rejected ( ) {
333+ let mut args = base_args ( ) ;
334+ args. no_cors = true ;
335+ args. cors_allow_origin = vec ! [ "http://localhost:5173" . to_string( ) ] ;
336+
337+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
338+
339+ let ConfigError :: InvalidArgument ( message) = error else {
340+ panic ! ( "Expected ConfigError::InvalidArgument, got {error:?}" ) ;
341+ } ;
342+ assert ! ( message. contains( "mutually exclusive" ) , "expected 'mutually exclusive' in: {message}" ) ;
343+ }
344+
345+ #[ test]
346+ fn test_skip_fee_field_validation_disables_validation ( ) {
347+ let mut args = base_args ( ) ;
348+ args. skip_fee_field_validation = true ;
349+
350+ let config = ServiceConfig :: from_args ( args) . unwrap ( ) ;
351+
352+ assert ! ( !config. prover_config. validate_zero_fee_fields) ;
353+ }
354+
355+ #[ test]
356+ fn test_no_cors_clears_config_file_origins ( ) {
357+ let mut config_file = NamedTempFile :: new ( ) . unwrap ( ) ;
358+ write ! (
359+ config_file,
360+ r#"{{"rpc_node_url":"http://localhost:9545","cors_allow_origin":["http://localhost:5173"]}}"# ,
361+ )
362+ . unwrap ( ) ;
363+
364+ let mut args = base_args ( ) ;
365+ args. config_file = Some ( config_file. path ( ) . to_path_buf ( ) ) ;
366+ args. rpc_url = None ;
367+ args. no_cors = true ;
368+
369+ let config = ServiceConfig :: from_args ( args) . unwrap ( ) ;
370+
371+ assert ! ( config. cors_allow_origin. is_empty( ) ) ;
372+ }
373+
374+ #[ test]
375+ fn test_config_file_values_used_when_no_cli_overrides ( ) {
376+ let mut config_file = NamedTempFile :: new ( ) . unwrap ( ) ;
377+ write ! (
378+ config_file,
379+ r#"{{"rpc_node_url":"http://localhost:9545","port":8080,"ip":"127.0.0.1"}}"# ,
380+ )
381+ . unwrap ( ) ;
382+
383+ let mut args = base_args ( ) ;
384+ args. config_file = Some ( config_file. path ( ) . to_path_buf ( ) ) ;
385+ args. rpc_url = None ;
386+
387+ let config = ServiceConfig :: from_args ( args) . unwrap ( ) ;
388+
389+ assert_eq ! ( config. port, 8080 ) ;
390+ assert_eq ! ( config. ip, "127.0.0.1" . parse:: <std:: net:: IpAddr >( ) . unwrap( ) ) ;
391+ }
392+
393+ #[ test]
394+ fn test_cli_overrides_config_file_values ( ) {
395+ let mut config_file = NamedTempFile :: new ( ) . unwrap ( ) ;
396+ write ! ( config_file, r#"{{"rpc_node_url":"http://localhost:9545","port":8080}}"# , ) . unwrap ( ) ;
397+
398+ let mut args = base_args ( ) ;
399+ args. config_file = Some ( config_file. path ( ) . to_path_buf ( ) ) ;
400+ args. rpc_url = None ;
401+ args. port = Some ( 9090 ) ;
402+
403+ let config = ServiceConfig :: from_args ( args) . unwrap ( ) ;
404+
405+ assert_eq ! ( config. port, 9090 ) ;
406+ }
407+
408+ #[ test]
409+ fn test_cors_rejects_non_http_scheme ( ) {
410+ let mut args = base_args ( ) ;
411+ args. cors_allow_origin = vec ! [ "ftp://example.com" . to_string( ) ] ;
412+
413+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
414+
415+ assert_matches ! ( error, ConfigError :: InvalidArgument ( _) ) ;
416+ }
417+
418+ #[ test]
419+ fn test_cors_rejects_origin_with_userinfo ( ) {
420+ let mut args = base_args ( ) ;
421+ args. cors_allow_origin = vec ! [ "http://user@example.com" . to_string( ) ] ;
422+
423+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
424+
425+ assert_matches ! ( error, ConfigError :: InvalidArgument ( _) ) ;
426+ }
0 commit comments