@@ -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 ;
@@ -285,3 +286,130 @@ fn env_var_sets_tls_key_file() {
285286
286287 assert_eq ! ( args. tls_key_file, Some ( PathBuf :: from( "/etc/ssl/key.pem" ) ) ) ;
287288}
289+
290+ #[ test]
291+ fn test_missing_rpc_url_rejected ( ) {
292+ let mut args = base_args ( ) ;
293+ args. rpc_url = None ; // No CLI arg, and no config file, so default empty rpc_node_url is used.
294+
295+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
296+
297+ assert_matches ! ( error, ConfigError :: MissingRequiredField ( _) ) ;
298+ }
299+
300+ #[ test]
301+ fn test_max_concurrent_requests_zero_rejected ( ) {
302+ let mut args = base_args ( ) ;
303+ args. max_concurrent_requests = Some ( 0 ) ;
304+
305+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
306+
307+ assert_matches ! ( error, ConfigError :: InvalidArgument ( _) ) ;
308+ }
309+
310+ #[ test]
311+ fn test_max_connections_zero_rejected ( ) {
312+ let mut args = base_args ( ) ;
313+ args. max_connections = Some ( 0 ) ;
314+
315+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
316+
317+ assert_matches ! ( error, ConfigError :: InvalidArgument ( _) ) ;
318+ }
319+
320+ #[ test]
321+ fn test_no_cors_with_cors_allow_origin_rejected ( ) {
322+ let mut args = base_args ( ) ;
323+ args. no_cors = true ;
324+ args. cors_allow_origin = vec ! [ "http://localhost:5173" . to_string( ) ] ;
325+
326+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
327+
328+ let ConfigError :: InvalidArgument ( message) = error else {
329+ panic ! ( "Expected ConfigError::InvalidArgument, got {error:?}" ) ;
330+ } ;
331+ assert ! ( message. contains( "mutually exclusive" ) , "expected 'mutually exclusive' in: {message}" ) ;
332+ }
333+
334+ #[ test]
335+ fn test_skip_fee_field_validation_disables_validation ( ) {
336+ let mut args = base_args ( ) ;
337+ args. skip_fee_field_validation = true ;
338+
339+ let config = ServiceConfig :: from_args ( args) . unwrap ( ) ;
340+
341+ assert ! ( !config. prover_config. validate_zero_fee_fields) ;
342+ }
343+
344+ #[ test]
345+ fn test_no_cors_clears_config_file_origins ( ) {
346+ let mut config_file = NamedTempFile :: new ( ) . unwrap ( ) ;
347+ write ! (
348+ config_file,
349+ r#"{{"rpc_node_url":"http://localhost:9545","cors_allow_origin":["http://localhost:5173"]}}"# ,
350+ )
351+ . unwrap ( ) ;
352+
353+ let mut args = base_args ( ) ;
354+ args. config_file = Some ( config_file. path ( ) . to_path_buf ( ) ) ;
355+ args. rpc_url = None ;
356+ args. no_cors = true ;
357+
358+ let config = ServiceConfig :: from_args ( args) . unwrap ( ) ;
359+
360+ assert ! ( config. cors_allow_origin. is_empty( ) ) ;
361+ }
362+
363+ #[ test]
364+ fn test_config_file_values_used_when_no_cli_overrides ( ) {
365+ let mut config_file = NamedTempFile :: new ( ) . unwrap ( ) ;
366+ write ! (
367+ config_file,
368+ r#"{{"rpc_node_url":"http://localhost:9545","port":8080,"ip":"127.0.0.1"}}"# ,
369+ )
370+ . unwrap ( ) ;
371+
372+ let mut args = base_args ( ) ;
373+ args. config_file = Some ( config_file. path ( ) . to_path_buf ( ) ) ;
374+ args. rpc_url = None ;
375+
376+ let config = ServiceConfig :: from_args ( args) . unwrap ( ) ;
377+
378+ assert_eq ! ( config. port, 8080 ) ;
379+ assert_eq ! ( config. ip, "127.0.0.1" . parse:: <std:: net:: IpAddr >( ) . unwrap( ) ) ;
380+ }
381+
382+ #[ test]
383+ fn test_cli_overrides_config_file_values ( ) {
384+ let mut config_file = NamedTempFile :: new ( ) . unwrap ( ) ;
385+ write ! ( config_file, r#"{{"rpc_node_url":"http://localhost:9545","port":8080}}"# , ) . unwrap ( ) ;
386+
387+ let mut args = base_args ( ) ;
388+ args. config_file = Some ( config_file. path ( ) . to_path_buf ( ) ) ;
389+ args. rpc_url = None ;
390+ args. port = Some ( 9090 ) ;
391+
392+ let config = ServiceConfig :: from_args ( args) . unwrap ( ) ;
393+
394+ assert_eq ! ( config. port, 9090 ) ;
395+ }
396+
397+ #[ test]
398+ fn test_cors_rejects_non_http_scheme ( ) {
399+ let mut args = base_args ( ) ;
400+ args. cors_allow_origin = vec ! [ "ftp://example.com" . to_string( ) ] ;
401+
402+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
403+
404+ assert_matches ! ( error, ConfigError :: InvalidArgument ( _) ) ;
405+ }
406+
407+ #[ test]
408+ fn test_cors_rejects_origin_with_userinfo ( ) {
409+ let mut args = base_args ( ) ;
410+ args. cors_allow_origin = vec ! [ "http://user@example.com" . to_string( ) ] ;
411+
412+ let error = ServiceConfig :: from_args ( args) . unwrap_err ( ) ;
413+
414+ assert_matches ! ( error, ConfigError :: InvalidArgument ( _) ) ;
415+ }
0 commit comments