diff options
Diffstat (limited to 'src/config.rs')
| -rw-r--r-- | src/config.rs | 261 |
1 files changed, 193 insertions, 68 deletions
diff --git a/src/config.rs b/src/config.rs index 8a9eb4d..37b1c1e 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -85,16 +85,25 @@ impl WhitelistEntry { | |||
| 85 | } | 85 | } |
| 86 | 86 | ||
| 87 | /// Parse whitelist from comma-separated string | 87 | /// Parse whitelist from comma-separated string |
| 88 | pub fn parse_whitelist(input: &str) -> Result<Vec<Self>> { | 88 | /// |
| 89 | /// Skips invalid entries with warnings instead of failing. | ||
| 90 | /// This allows the config to load even if some whitelist entries are malformed. | ||
| 91 | pub fn parse_whitelist(input: &str) -> Vec<Self> { | ||
| 89 | if input.trim().is_empty() { | 92 | if input.trim().is_empty() { |
| 90 | return Ok(Vec::new()); | 93 | return Vec::new(); |
| 91 | } | 94 | } |
| 92 | 95 | ||
| 93 | input | 96 | input |
| 94 | .split(',') | 97 | .split(',') |
| 95 | .map(|s| s.trim()) | 98 | .map(|s| s.trim()) |
| 96 | .filter(|s| !s.is_empty()) | 99 | .filter(|s| !s.is_empty()) |
| 97 | .map(Self::parse) | 100 | .filter_map(|s| match Self::parse(s) { |
| 101 | Ok(entry) => Some(entry), | ||
| 102 | Err(e) => { | ||
| 103 | tracing::warn!("Skipping invalid whitelist entry '{}': {}", s, e); | ||
| 104 | None | ||
| 105 | } | ||
| 106 | }) | ||
| 98 | .collect() | 107 | .collect() |
| 99 | } | 108 | } |
| 100 | } | 109 | } |
| @@ -464,23 +473,60 @@ impl Config { | |||
| 464 | } | 473 | } |
| 465 | } | 474 | } |
| 466 | 475 | ||
| 476 | /// Validate configuration and return fatal errors | ||
| 477 | /// | ||
| 478 | /// This should be called immediately after Config::load() to fail fast on config errors. | ||
| 479 | /// Recoverable issues (e.g., malformed whitelist entries) are logged as warnings and skipped. | ||
| 480 | pub fn validate(&self) -> Result<()> { | ||
| 481 | // Validate relay owner nsec (should always be set by Config::load()) | ||
| 482 | let nsec = self | ||
| 483 | .relay_owner_nsec | ||
| 484 | .as_ref() | ||
| 485 | .context("relay_owner_nsec not set (should be set by Config::load())")?; | ||
| 486 | Keys::parse(nsec).context("Invalid relay_owner_nsec format")?; | ||
| 487 | |||
| 488 | // Validate archive configuration | ||
| 489 | let archive_whitelist = WhitelistEntry::parse_whitelist(&self.archive_whitelist); | ||
| 490 | let archive_enabled = self.archive_all || !archive_whitelist.is_empty(); | ||
| 491 | |||
| 492 | // Fatal error: archive_read_only=true without archive mode enabled | ||
| 493 | if let Some(true) = self.archive_read_only { | ||
| 494 | if !archive_enabled { | ||
| 495 | return Err(anyhow!( | ||
| 496 | "NGIT_ARCHIVE_READ_ONLY=true requires either NGIT_ARCHIVE_ALL=true or NGIT_ARCHIVE_WHITELIST to be set" | ||
| 497 | )); | ||
| 498 | } | ||
| 499 | } | ||
| 500 | |||
| 501 | // Validate repository whitelist configuration | ||
| 502 | let repository_whitelist = WhitelistEntry::parse_whitelist(&self.repository_whitelist); | ||
| 503 | |||
| 504 | // Fatal error: repository_whitelist with archive_read_only=true (incompatible) | ||
| 505 | if !repository_whitelist.is_empty() { | ||
| 506 | let read_only = self.archive_read_only.unwrap_or(archive_enabled); | ||
| 507 | if read_only { | ||
| 508 | return Err(anyhow!( | ||
| 509 | "NGIT_REPOSITORY_WHITELIST cannot be used with NGIT_ARCHIVE_READ_ONLY=true. \ | ||
| 510 | Archive read-only mode rejects announcements that don't match the archive whitelist, \ | ||
| 511 | regardless of service listing. Either set NGIT_ARCHIVE_READ_ONLY=false or use \ | ||
| 512 | NGIT_ARCHIVE_WHITELIST instead of NGIT_REPOSITORY_WHITELIST." | ||
| 513 | )); | ||
| 514 | } | ||
| 515 | } | ||
| 516 | |||
| 517 | Ok(()) | ||
| 518 | } | ||
| 519 | |||
| 467 | /// Get parsed archive configuration with computed read-only mode | 520 | /// Get parsed archive configuration with computed read-only mode |
| 468 | /// | 521 | /// |
| 469 | /// Read-only mode defaults to true if archive mode is enabled, false otherwise. | 522 | /// Read-only mode defaults to true if archive mode is enabled, false otherwise. |
| 470 | /// Throws error if explicitly set to true without archive mode enabled. | 523 | /// This method assumes config has been validated - call Config::validate() first! |
| 471 | pub fn archive_config(&self) -> Result<ArchiveConfig> { | 524 | pub fn archive_config(&self) -> ArchiveConfig { |
| 472 | let whitelist = WhitelistEntry::parse_whitelist(&self.archive_whitelist)?; | 525 | let whitelist = WhitelistEntry::parse_whitelist(&self.archive_whitelist); |
| 473 | let archive_enabled = self.archive_all || !whitelist.is_empty(); | 526 | let archive_enabled = self.archive_all || !whitelist.is_empty(); |
| 474 | 527 | ||
| 475 | let read_only = match self.archive_read_only { | 528 | let read_only = match self.archive_read_only { |
| 476 | Some(true) => { | 529 | Some(true) => true, // Already validated in validate() |
| 477 | if !archive_enabled { | ||
| 478 | return Err(anyhow!( | ||
| 479 | "NGIT_ARCHIVE_READ_ONLY=true requires either NGIT_ARCHIVE_ALL=true or NGIT_ARCHIVE_WHITELIST to be set" | ||
| 480 | )); | ||
| 481 | } | ||
| 482 | true | ||
| 483 | } | ||
| 484 | Some(false) => false, | 530 | Some(false) => false, |
| 485 | None => { | 531 | None => { |
| 486 | // Default: true if archive mode enabled, false otherwise | 532 | // Default: true if archive mode enabled, false otherwise |
| @@ -488,33 +534,19 @@ impl Config { | |||
| 488 | } | 534 | } |
| 489 | }; | 535 | }; |
| 490 | 536 | ||
| 491 | Ok(ArchiveConfig { | 537 | ArchiveConfig { |
| 492 | archive_all: self.archive_all, | 538 | archive_all: self.archive_all, |
| 493 | whitelist, | 539 | whitelist, |
| 494 | read_only, | 540 | read_only, |
| 495 | }) | 541 | } |
| 496 | } | 542 | } |
| 497 | 543 | ||
| 498 | /// Get parsed repository whitelist configuration | 544 | /// Get parsed repository whitelist configuration |
| 499 | /// | 545 | /// |
| 500 | /// Throws error if repository_whitelist is set together with archive_read_only=true | 546 | /// This method assumes config has been validated - call Config::validate() first! |
| 501 | pub fn repository_config(&self) -> Result<RepositoryConfig> { | 547 | pub fn repository_config(&self) -> RepositoryConfig { |
| 502 | let whitelist = WhitelistEntry::parse_whitelist(&self.repository_whitelist)?; | 548 | let whitelist = WhitelistEntry::parse_whitelist(&self.repository_whitelist); |
| 503 | 549 | RepositoryConfig { whitelist } | |
| 504 | // Validate incompatible configurations | ||
| 505 | if !whitelist.is_empty() { | ||
| 506 | let archive_config = self.archive_config()?; | ||
| 507 | if archive_config.read_only { | ||
| 508 | return Err(anyhow!( | ||
| 509 | "NGIT_REPOSITORY_WHITELIST cannot be used with NGIT_ARCHIVE_READ_ONLY=true. \ | ||
| 510 | Archive read-only mode rejects announcements that don't match the archive whitelist, \ | ||
| 511 | regardless of service listing. Either set NGIT_ARCHIVE_READ_ONLY=false or use \ | ||
| 512 | NGIT_ARCHIVE_WHITELIST instead of NGIT_REPOSITORY_WHITELIST." | ||
| 513 | )); | ||
| 514 | } | ||
| 515 | } | ||
| 516 | |||
| 517 | Ok(RepositoryConfig { whitelist }) | ||
| 518 | } | 550 | } |
| 519 | 551 | ||
| 520 | /// Create config for testing | 552 | /// Create config for testing |
| @@ -807,10 +839,10 @@ mod tests { | |||
| 807 | 839 | ||
| 808 | #[test] | 840 | #[test] |
| 809 | fn test_parse_whitelist_empty() { | 841 | fn test_parse_whitelist_empty() { |
| 810 | let whitelist = WhitelistEntry::parse_whitelist("").unwrap(); | 842 | let whitelist = WhitelistEntry::parse_whitelist(""); |
| 811 | assert!(whitelist.is_empty()); | 843 | assert!(whitelist.is_empty()); |
| 812 | 844 | ||
| 813 | let whitelist = WhitelistEntry::parse_whitelist(" ").unwrap(); | 845 | let whitelist = WhitelistEntry::parse_whitelist(" "); |
| 814 | assert!(whitelist.is_empty()); | 846 | assert!(whitelist.is_empty()); |
| 815 | } | 847 | } |
| 816 | 848 | ||
| @@ -823,12 +855,22 @@ mod tests { | |||
| 823 | let whitelist = WhitelistEntry::parse_whitelist(&format!( | 855 | let whitelist = WhitelistEntry::parse_whitelist(&format!( |
| 824 | "{},bitcoin-core,{}/linux", | 856 | "{},bitcoin-core,{}/linux", |
| 825 | test_npub1, test_npub2 | 857 | test_npub1, test_npub2 |
| 826 | )) | 858 | )); |
| 827 | .unwrap(); | ||
| 828 | assert_eq!(whitelist.len(), 3); | 859 | assert_eq!(whitelist.len(), 3); |
| 829 | } | 860 | } |
| 830 | 861 | ||
| 831 | #[test] | 862 | #[test] |
| 863 | fn test_parse_whitelist_invalid_npub_skipped() { | ||
| 864 | // Invalid entries should be skipped with warnings, not fail | ||
| 865 | let whitelist = WhitelistEntry::parse_whitelist("npub1invalid,bitcoin-core"); | ||
| 866 | assert_eq!(whitelist.len(), 1); // Only bitcoin-core should be parsed | ||
| 867 | assert!(matches!( | ||
| 868 | &whitelist[0], | ||
| 869 | WhitelistEntry::Identifier(id) if id == "bitcoin-core" | ||
| 870 | )); | ||
| 871 | } | ||
| 872 | |||
| 873 | #[test] | ||
| 832 | fn test_archive_config_parsing() { | 874 | fn test_archive_config_parsing() { |
| 833 | let keys = Keys::generate(); | 875 | let keys = Keys::generate(); |
| 834 | let test_npub = keys.public_key().to_bech32().unwrap(); | 876 | let test_npub = keys.public_key().to_bech32().unwrap(); |
| @@ -836,31 +878,22 @@ mod tests { | |||
| 836 | archive_whitelist: format!("{},bitcoin-core", test_npub), | 878 | archive_whitelist: format!("{},bitcoin-core", test_npub), |
| 837 | ..Config::for_testing() | 879 | ..Config::for_testing() |
| 838 | }; | 880 | }; |
| 839 | let archive_config = config.archive_config().unwrap(); | 881 | let archive_config = config.archive_config(); |
| 840 | assert_eq!(archive_config.whitelist.len(), 2); | 882 | assert_eq!(archive_config.whitelist.len(), 2); |
| 841 | } | 883 | } |
| 842 | 884 | ||
| 843 | #[test] | 885 | #[test] |
| 844 | fn test_archive_config_invalid_npub() { | ||
| 845 | let config = Config { | ||
| 846 | archive_whitelist: "npub1invalid".to_string(), | ||
| 847 | ..Config::for_testing() | ||
| 848 | }; | ||
| 849 | assert!(config.archive_config().is_err()); | ||
| 850 | } | ||
| 851 | |||
| 852 | #[test] | ||
| 853 | fn test_archive_read_only_defaults() { | 886 | fn test_archive_read_only_defaults() { |
| 854 | // Default: false when no archive mode | 887 | // Default: false when no archive mode |
| 855 | let config = Config::for_testing(); | 888 | let config = Config::for_testing(); |
| 856 | assert_eq!(config.archive_config().unwrap().read_only, false); | 889 | assert_eq!(config.archive_config().read_only, false); |
| 857 | 890 | ||
| 858 | // Default: true when archive_all is set | 891 | // Default: true when archive_all is set |
| 859 | let config = Config { | 892 | let config = Config { |
| 860 | archive_all: true, | 893 | archive_all: true, |
| 861 | ..Config::for_testing() | 894 | ..Config::for_testing() |
| 862 | }; | 895 | }; |
| 863 | assert_eq!(config.archive_config().unwrap().read_only, true); | 896 | assert_eq!(config.archive_config().read_only, true); |
| 864 | 897 | ||
| 865 | // Default: true when archive_whitelist is set | 898 | // Default: true when archive_whitelist is set |
| 866 | let keys = Keys::generate(); | 899 | let keys = Keys::generate(); |
| @@ -869,7 +902,7 @@ mod tests { | |||
| 869 | archive_whitelist: test_npub, | 902 | archive_whitelist: test_npub, |
| 870 | ..Config::for_testing() | 903 | ..Config::for_testing() |
| 871 | }; | 904 | }; |
| 872 | assert_eq!(config.archive_config().unwrap().read_only, true); | 905 | assert_eq!(config.archive_config().read_only, true); |
| 873 | } | 906 | } |
| 874 | 907 | ||
| 875 | #[test] | 908 | #[test] |
| @@ -880,7 +913,7 @@ mod tests { | |||
| 880 | archive_read_only: Some(true), | 913 | archive_read_only: Some(true), |
| 881 | ..Config::for_testing() | 914 | ..Config::for_testing() |
| 882 | }; | 915 | }; |
| 883 | assert_eq!(config.archive_config().unwrap().read_only, true); | 916 | assert_eq!(config.archive_config().read_only, true); |
| 884 | 917 | ||
| 885 | // Explicit false with archive_all (unusual but allowed) | 918 | // Explicit false with archive_all (unusual but allowed) |
| 886 | let config = Config { | 919 | let config = Config { |
| @@ -888,29 +921,26 @@ mod tests { | |||
| 888 | archive_read_only: Some(false), | 921 | archive_read_only: Some(false), |
| 889 | ..Config::for_testing() | 922 | ..Config::for_testing() |
| 890 | }; | 923 | }; |
| 891 | assert_eq!(config.archive_config().unwrap().read_only, false); | 924 | assert_eq!(config.archive_config().read_only, false); |
| 892 | 925 | ||
| 893 | // Explicit false without archive mode | 926 | // Explicit false without archive mode |
| 894 | let config = Config { | 927 | let config = Config { |
| 895 | archive_read_only: Some(false), | 928 | archive_read_only: Some(false), |
| 896 | ..Config::for_testing() | 929 | ..Config::for_testing() |
| 897 | }; | 930 | }; |
| 898 | assert_eq!(config.archive_config().unwrap().read_only, false); | 931 | assert_eq!(config.archive_config().read_only, false); |
| 899 | } | 932 | } |
| 900 | 933 | ||
| 901 | #[test] | 934 | #[test] |
| 902 | fn test_archive_read_only_error() { | 935 | fn test_archive_read_only_validation_error() { |
| 903 | // Error: true without archive mode | 936 | // Error: true without archive mode should fail validation |
| 904 | let config = Config { | 937 | let config = Config { |
| 905 | archive_read_only: Some(true), | 938 | archive_read_only: Some(true), |
| 906 | ..Config::for_testing() | 939 | ..Config::for_testing() |
| 907 | }; | 940 | }; |
| 908 | assert!(config.archive_config().is_err()); | 941 | let result = config.validate(); |
| 909 | assert!(config | 942 | assert!(result.is_err()); |
| 910 | .archive_config() | 943 | assert!(result.unwrap_err().to_string().contains("requires either")); |
| 911 | .unwrap_err() | ||
| 912 | .to_string() | ||
| 913 | .contains("requires either")); | ||
| 914 | } | 944 | } |
| 915 | 945 | ||
| 916 | #[test] | 946 | #[test] |
| @@ -921,7 +951,7 @@ mod tests { | |||
| 921 | repository_whitelist: format!("{},bitcoin-core", test_npub), | 951 | repository_whitelist: format!("{},bitcoin-core", test_npub), |
| 922 | ..Config::for_testing() | 952 | ..Config::for_testing() |
| 923 | }; | 953 | }; |
| 924 | let repo_config = config.repository_config().unwrap(); | 954 | let repo_config = config.repository_config(); |
| 925 | assert_eq!(repo_config.whitelist.len(), 2); | 955 | assert_eq!(repo_config.whitelist.len(), 2); |
| 926 | assert!(repo_config.enabled()); | 956 | assert!(repo_config.enabled()); |
| 927 | } | 957 | } |
| @@ -929,13 +959,13 @@ mod tests { | |||
| 929 | #[test] | 959 | #[test] |
| 930 | fn test_repository_whitelist_empty() { | 960 | fn test_repository_whitelist_empty() { |
| 931 | let config = Config::for_testing(); | 961 | let config = Config::for_testing(); |
| 932 | let repo_config = config.repository_config().unwrap(); | 962 | let repo_config = config.repository_config(); |
| 933 | assert!(repo_config.whitelist.is_empty()); | 963 | assert!(repo_config.whitelist.is_empty()); |
| 934 | assert!(!repo_config.enabled()); | 964 | assert!(!repo_config.enabled()); |
| 935 | } | 965 | } |
| 936 | 966 | ||
| 937 | #[test] | 967 | #[test] |
| 938 | fn test_repository_whitelist_incompatible_with_archive_read_only() { | 968 | fn test_repository_whitelist_validation_incompatible_with_archive_read_only() { |
| 939 | let keys = Keys::generate(); | 969 | let keys = Keys::generate(); |
| 940 | let test_npub = keys.public_key().to_bech32().unwrap(); | 970 | let test_npub = keys.public_key().to_bech32().unwrap(); |
| 941 | let config = Config { | 971 | let config = Config { |
| @@ -944,7 +974,7 @@ mod tests { | |||
| 944 | repository_whitelist: test_npub, | 974 | repository_whitelist: test_npub, |
| 945 | ..Config::for_testing() | 975 | ..Config::for_testing() |
| 946 | }; | 976 | }; |
| 947 | let result = config.repository_config(); | 977 | let result = config.validate(); |
| 948 | assert!(result.is_err()); | 978 | assert!(result.is_err()); |
| 949 | let err = result.unwrap_err().to_string(); | 979 | let err = result.unwrap_err().to_string(); |
| 950 | assert!(err.contains("cannot be used with")); | 980 | assert!(err.contains("cannot be used with")); |
| @@ -961,8 +991,8 @@ mod tests { | |||
| 961 | repository_whitelist: test_npub, | 991 | repository_whitelist: test_npub, |
| 962 | ..Config::for_testing() | 992 | ..Config::for_testing() |
| 963 | }; | 993 | }; |
| 964 | // Should not error | 994 | // Should not error on validation |
| 965 | assert!(config.repository_config().is_ok()); | 995 | assert!(config.validate().is_ok()); |
| 966 | } | 996 | } |
| 967 | 997 | ||
| 968 | #[test] | 998 | #[test] |
| @@ -980,4 +1010,99 @@ mod tests { | |||
| 980 | assert!(config.matches("npub1bob", "bitcoin-core")); | 1010 | assert!(config.matches("npub1bob", "bitcoin-core")); |
| 981 | assert!(!config.matches("npub1bob", "other-repo")); | 1011 | assert!(!config.matches("npub1bob", "other-repo")); |
| 982 | } | 1012 | } |
| 1013 | |||
| 1014 | #[test] | ||
| 1015 | fn test_validate_success_with_valid_config() { | ||
| 1016 | // Valid config should pass validation | ||
| 1017 | let keys = Keys::generate(); | ||
| 1018 | let test_npub = keys.public_key().to_bech32().unwrap(); | ||
| 1019 | let config = Config { | ||
| 1020 | archive_whitelist: format!("{},bitcoin-core", test_npub), | ||
| 1021 | archive_read_only: Some(false), | ||
| 1022 | repository_whitelist: "rust".to_string(), | ||
| 1023 | ..Config::for_testing() | ||
| 1024 | }; | ||
| 1025 | assert!(config.validate().is_ok()); | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | #[test] | ||
| 1029 | fn test_validate_with_all_invalid_whitelist_entries() { | ||
| 1030 | // All invalid entries should be skipped with warnings, but validation should succeed | ||
| 1031 | let config = Config { | ||
| 1032 | archive_whitelist: "npub1invalid,npub1bad,npub1wrong".to_string(), | ||
| 1033 | ..Config::for_testing() | ||
| 1034 | }; | ||
| 1035 | assert!(config.validate().is_ok()); | ||
| 1036 | // All entries should be skipped | ||
| 1037 | let archive_config = config.archive_config(); | ||
| 1038 | assert_eq!(archive_config.whitelist.len(), 0); | ||
| 1039 | assert!(!archive_config.enabled()); | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | #[test] | ||
| 1043 | fn test_validate_with_mixed_valid_invalid_entries() { | ||
| 1044 | let keys = Keys::generate(); | ||
| 1045 | let test_npub = keys.public_key().to_bech32().unwrap(); | ||
| 1046 | // Mixed valid and invalid entries - should keep valid ones | ||
| 1047 | let config = Config { | ||
| 1048 | repository_whitelist: format!("npub1invalid,{},bitcoin-core,npub1bad", test_npub), | ||
| 1049 | ..Config::for_testing() | ||
| 1050 | }; | ||
| 1051 | assert!(config.validate().is_ok()); | ||
| 1052 | let repo_config = config.repository_config(); | ||
| 1053 | // Should have 2 valid entries: the test_npub and bitcoin-core | ||
| 1054 | assert_eq!(repo_config.whitelist.len(), 2); | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | #[test] | ||
| 1058 | fn test_whitelist_entry_with_extra_whitespace() { | ||
| 1059 | let keys = Keys::generate(); | ||
| 1060 | let test_npub = keys.public_key().to_bech32().unwrap(); | ||
| 1061 | // Whitespace should be trimmed | ||
| 1062 | let whitelist = | ||
| 1063 | WhitelistEntry::parse_whitelist(&format!(" {} , bitcoin-core , rust ", test_npub)); | ||
| 1064 | assert_eq!(whitelist.len(), 3); | ||
| 1065 | } | ||
| 1066 | |||
| 1067 | #[test] | ||
| 1068 | fn test_archive_config_with_all_invalid_entries_not_enabled() { | ||
| 1069 | // If all whitelist entries are invalid, archive mode should not be enabled | ||
| 1070 | let config = Config { | ||
| 1071 | archive_whitelist: "npub1invalid,npub1bad".to_string(), | ||
| 1072 | ..Config::for_testing() | ||
| 1073 | }; | ||
| 1074 | let archive_config = config.archive_config(); | ||
| 1075 | assert!(!archive_config.enabled()); | ||
| 1076 | assert_eq!(archive_config.whitelist.len(), 0); | ||
| 1077 | } | ||
| 1078 | |||
| 1079 | #[test] | ||
| 1080 | fn test_validate_detects_invalid_relay_owner_nsec() { | ||
| 1081 | // Invalid nsec should fail validation | ||
| 1082 | let config = Config { | ||
| 1083 | relay_owner_nsec: Some("nsec1invalid".to_string()), | ||
| 1084 | ..Config::for_testing() | ||
| 1085 | }; | ||
| 1086 | let result = config.validate(); | ||
| 1087 | assert!(result.is_err()); | ||
| 1088 | assert!(result | ||
| 1089 | .unwrap_err() | ||
| 1090 | .to_string() | ||
| 1091 | .contains("Invalid relay_owner_nsec")); | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | #[test] | ||
| 1095 | fn test_validate_requires_relay_owner_nsec() { | ||
| 1096 | // Missing nsec should fail validation | ||
| 1097 | let config = Config { | ||
| 1098 | relay_owner_nsec: None, | ||
| 1099 | ..Config::for_testing() | ||
| 1100 | }; | ||
| 1101 | let result = config.validate(); | ||
| 1102 | assert!(result.is_err()); | ||
| 1103 | assert!(result | ||
| 1104 | .unwrap_err() | ||
| 1105 | .to_string() | ||
| 1106 | .contains("relay_owner_nsec not set")); | ||
| 1107 | } | ||
| 983 | } | 1108 | } |