voa_config/config/technology/
settings.rs

1//! Handling of common items used in configuration objects.
2
3use std::{fmt::Display, path::PathBuf};
4
5use garde::Validate;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    Error,
10    config::technology::openpgp::{OpenpgpSettings, VerificationMethod},
11    file::{ConfigOpenpgpSettings, ConfigTechnologySettings, ConfigVerificationMethod},
12};
13
14/// A set of [`TechnologySettings`] that serve as defaults.
15///
16/// An [`TechnologySettingsDefaults`] is used when calling
17/// [`TechnologySettings::from_config_with_defaults`], which creates a new
18/// [`TechnologySettings`] from a [`ConfigTechnologySettings`].
19/// Here, an [`TechnologySettingsDefaults`] provides defaults for any unset field in
20/// [`ConfigTechnologySettings`].
21///
22/// # Note
23///
24/// When creating the [`TechnologySettings`] for an OS-level environment, the `os_defaults` field
25/// should be unset. When creating the [`TechnologySettings`] for a context-level environment, the
26/// `os_defaults` field should be set.
27#[derive(Clone, Copy, Debug)]
28pub(crate) struct TechnologySettingsDefaults<'a> {
29    pub(crate) built_in_defaults: &'a TechnologySettings,
30    pub(crate) os_defaults: Option<&'a TechnologySettings>,
31}
32
33impl<'a> TechnologySettingsDefaults<'a> {
34    /// Returns a matching [`OpenpgpSettings`] for a [`ConfigOpenpgpSettings`].
35    ///
36    /// Settings are considered to be matching if their verification method is the same.
37    pub(crate) fn matching_openpgp_settings(
38        &self,
39        config: &ConfigOpenpgpSettings,
40    ) -> Option<(&OpenpgpSettings, &[ConfigOrigin])> {
41        match config.config_verification_method() {
42            ConfigVerificationMethod::Plain(_) => {
43                if matches!(
44                    self.os_defaults,
45                    Some(TechnologySettings {
46                        openpgp: OpenpgpSettings {
47                            verification_method: VerificationMethod::Plain(_),
48                            ..
49                        },
50                        ..
51                    })
52                ) {
53                    self.os_defaults
54                        .map(|settings| (settings.openpgp_settings(), settings.origins()))
55                } else if matches!(
56                    self.built_in_defaults,
57                    TechnologySettings {
58                        openpgp: OpenpgpSettings {
59                            verification_method: VerificationMethod::Plain(_),
60                            ..
61                        },
62                        ..
63                    }
64                ) {
65                    Some((
66                        self.built_in_defaults.openpgp_settings(),
67                        self.built_in_defaults.origins(),
68                    ))
69                } else {
70                    None
71                }
72            }
73            ConfigVerificationMethod::TrustAnchor(_) => {
74                if matches!(
75                    self.os_defaults,
76                    Some(TechnologySettings {
77                        openpgp: OpenpgpSettings {
78                            verification_method: VerificationMethod::TrustAnchor(_),
79                            ..
80                        },
81                        ..
82                    })
83                ) {
84                    self.os_defaults
85                        .map(|settings| (settings.openpgp_settings(), settings.origins()))
86                } else if matches!(
87                    self.built_in_defaults,
88                    TechnologySettings {
89                        openpgp: OpenpgpSettings {
90                            verification_method: VerificationMethod::TrustAnchor(_),
91                            ..
92                        },
93                        ..
94                    }
95                ) {
96                    Some((
97                        self.built_in_defaults.openpgp_settings(),
98                        self.built_in_defaults.origins(),
99                    ))
100                } else {
101                    None
102                }
103            }
104            ConfigVerificationMethod::WebOfTrust(_) => {
105                if matches!(
106                    self.os_defaults,
107                    Some(TechnologySettings {
108                        openpgp: OpenpgpSettings {
109                            verification_method: VerificationMethod::WebOfTrust(_),
110                            ..
111                        },
112                        ..
113                    })
114                ) {
115                    self.os_defaults
116                        .map(|settings| (settings.openpgp_settings(), settings.origins()))
117                } else if matches!(
118                    self.built_in_defaults,
119                    TechnologySettings {
120                        openpgp: OpenpgpSettings {
121                            verification_method: VerificationMethod::WebOfTrust(_),
122                            ..
123                        },
124                        ..
125                    }
126                ) {
127                    Some((
128                        self.built_in_defaults.openpgp_settings(),
129                        self.built_in_defaults.origins(),
130                    ))
131                } else {
132                    None
133                }
134            }
135        }
136    }
137}
138
139/// The origin of a config file.
140#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
141#[serde(rename_all = "snake_case")]
142pub enum ConfigOrigin {
143    /// The path to a config file.
144    ConfigFile(PathBuf),
145
146    /// The path to a drop-in config file.
147    DropInFile(PathBuf),
148
149    /// Built-in defaults.
150    Default,
151}
152
153impl Display for ConfigOrigin {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        match self {
156            Self::ConfigFile(path) => write!(f, "Config file: {}", path.to_string_lossy()),
157            Self::DropInFile(path) => {
158                write!(f, "Drop-in config file: {}", path.to_string_lossy())
159            }
160            Self::Default => write!(f, "Built-in defaults"),
161        }
162    }
163}
164
165/// Settings for all supported cryptographic technologies.
166///
167/// Describes settings for all supported cryptographic technologies in a configuration object.
168/// May be used on a system, OS or context level.
169#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Validate)]
170pub struct TechnologySettings {
171    #[garde(skip)]
172    pub(crate) origins: Vec<ConfigOrigin>,
173
174    #[garde(dive)]
175    openpgp: OpenpgpSettings,
176}
177
178impl TechnologySettings {
179    /// Creates a new [`TechnologySettings`] from an [`OpenpgpSettings`].
180    pub fn new(origins: Vec<ConfigOrigin>, openpgp: OpenpgpSettings) -> Self {
181        Self { origins, openpgp }
182    }
183
184    /// Returns a reference to the list of origins that were involved in the creation of the
185    /// [`TechnologySettings`].
186    pub fn origins(&self) -> &[ConfigOrigin] {
187        &self.origins
188    }
189
190    /// Creates a new [`TechnologySettings`] from a [`ConfigTechnologySettings`] and `defaults` for
191    /// any unset fields.
192    ///
193    /// # Errors
194    ///
195    /// Returns an error, if a validated [`OpenpgpSettings`] cannot be created from the `config` and
196    /// the set of `defaults`.
197    pub(crate) fn from_config_with_defaults(
198        config: &ConfigTechnologySettings,
199        defaults: TechnologySettingsDefaults,
200    ) -> Result<Self, Error> {
201        let (openpgp_settings, origins) =
202            if let Some(config_openpgp_settings) = config.config_openpgp_settings() {
203                // If the config provides OpenPGP settings, attempt to use matching OpenPGP settings
204                // from the technology settings defaults to populate any unset fields.
205                let (openpgp_settings_defaults, origins) = if let Some(openpgp_settings) =
206                    defaults.matching_openpgp_settings(config_openpgp_settings)
207                {
208                    openpgp_settings
209                // If no matching OpenPGP settings are found in the settings defaults, use default
210                // OpenpgpSettings.
211                } else {
212                    (
213                        &OpenpgpSettings::default(),
214                        [ConfigOrigin::Default].as_slice(),
215                    )
216                };
217
218                (
219                    OpenpgpSettings::from_config_with_defaults(
220                        config_openpgp_settings,
221                        openpgp_settings_defaults,
222                    )?,
223                    origins,
224                )
225            // If the config provides no OpenPGP settings, use the OS-level defaults or fall
226            // back to the system-level defaults.
227            } else if let Some(os_level) = defaults.os_defaults {
228                (os_level.openpgp_settings().clone(), os_level.origins())
229            } else {
230                (
231                    defaults.built_in_defaults.openpgp_settings().clone(),
232                    defaults.built_in_defaults.origins(),
233                )
234            };
235        // Prepend the config origins.
236        let origins = [config.origins(), origins].concat();
237
238        Ok(TechnologySettings::new(origins, openpgp_settings))
239    }
240
241    /// Creates a new [`TechnologySettings`] from a YAML string.
242    pub fn from_yaml_str(s: &str) -> Result<Self, Error> {
243        let settings: Self =
244            serde_saphyr::from_str(s).map_err(|source| Error::YamlDeserialize {
245                context: "deserializing a YAML-based system-level configuration string".to_string(),
246                source,
247            })?;
248
249        settings.validate().map_err(|source| Error::Validation {
250            context: "creating technology settings from a YAML string".to_string(),
251            source,
252        })?;
253
254        Ok(settings)
255    }
256
257    /// Returns a reference to the tracked [`OpenpgpSettings`].
258    pub fn openpgp_settings(&self) -> &OpenpgpSettings {
259        &self.openpgp
260    }
261}
262
263impl Default for TechnologySettings {
264    fn default() -> Self {
265        Self {
266            origins: vec![ConfigOrigin::Default],
267            openpgp: OpenpgpSettings::default(),
268        }
269    }
270}
271
272impl Display for TechnologySettings {
273    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274        writeln!(f, "{}", self.openpgp_settings())?;
275
276        if !self.origins().is_empty() {
277            writeln!(
278                f,
279                "📝 The following sources have been considered for the creation of the settings:"
280            )?;
281            for origin in self.origins().iter() {
282                writeln!(f, "⤷ {origin}")?;
283            }
284        }
285
286        Ok(())
287    }
288}
289
290#[cfg(test)]
291mod tests {
292    use std::{collections::HashSet, num::NonZeroUsize, thread::current};
293
294    use insta::{assert_snapshot, with_settings};
295    use rstest::rstest;
296    use testresult::TestResult;
297
298    use super::*;
299    use crate::{
300        file::{ConfigTrustAnchorMode, ConfigWebOfTrustMode},
301        openpgp::{
302            NumCertifications,
303            NumDataSignatures,
304            PlainMode,
305            TrustAmountFlow,
306            TrustAmountPartial,
307            TrustAmountRoot,
308            TrustAnchorMode,
309            WebOfTrustMode,
310            WebOfTrustRoot,
311        },
312    };
313
314    const SNAPSHOT_PATH: &str = "fixtures/settings/";
315
316    /// Ensures that [`TechnologySettings::from_config_with_defaults`] succeeds.
317    #[rstest]
318    #[case::config_without_openpgp_settings_overriden_with_system_level(
319        ConfigTechnologySettings::new(
320            vec![ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml"))],
321            None,
322        ),
323        TechnologySettingsDefaults{
324            built_in_defaults: &TechnologySettings::new(
325                vec![ConfigOrigin::ConfigFile(PathBuf::from("/usr/share/voa/example.yaml"))],
326                OpenpgpSettings::new(
327                    NumDataSignatures::new(NonZeroUsize::new(2).expect("2 is larger than 0")),
328                    VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
329                )?
330            ),
331            os_defaults: None,
332        },
333        TechnologySettings::new(
334            vec![
335                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml")),
336                ConfigOrigin::ConfigFile(PathBuf::from("/usr/share/voa/example.yaml")),
337            ],
338            OpenpgpSettings::new(
339                NumDataSignatures::new(NonZeroUsize::new(2).expect("2 is larger than 0")),
340                VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
341            )?
342        )
343    )]
344    #[case::config_without_openpgp_settings_overriden_with_os_level(
345        ConfigTechnologySettings::new(
346            vec![ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/20-example.yaml"))],
347            None,
348        ),
349        TechnologySettingsDefaults{
350            built_in_defaults: &TechnologySettings::new(
351                vec![ConfigOrigin::ConfigFile(PathBuf::from("/usr/share/voa/example.yaml"))],
352                OpenpgpSettings::new(
353                    NumDataSignatures::new(NonZeroUsize::new(2).expect("2 is larger than 0")),
354                    VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
355                )?
356            ),
357            os_defaults: Some(&TechnologySettings::new(
358                vec![ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml"))],
359                OpenpgpSettings::new(
360                    NumDataSignatures::new(NonZeroUsize::new(3).expect("2 is larger than 0")),
361                    VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
362                )?
363            )),
364        },
365        TechnologySettings::new(
366            vec![
367                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/20-example.yaml")),
368                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml")),
369            ],
370            OpenpgpSettings::new(
371                NumDataSignatures::new(NonZeroUsize::new(3).expect("2 is larger than 0")),
372                VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
373            )?
374        )
375    )]
376    #[case::config_with_wot_openpgp_settings_overridden_by_generic_defaults(
377        ConfigTechnologySettings::new(
378            vec![ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/20-example.yaml"))],
379            Some(ConfigOpenpgpSettings::new(
380                Some(NumDataSignatures::new(NonZeroUsize::new(1).expect("1 is larger than 0"))),
381                ConfigVerificationMethod::WebOfTrust(ConfigWebOfTrustMode::new(None, None, Some(HashSet::new()), Some(HashSet::new())))
382            )?
383        )),
384        TechnologySettingsDefaults{
385            built_in_defaults: &TechnologySettings::new(
386                vec![ConfigOrigin::ConfigFile(PathBuf::from("/usr/share/voa/example.yaml"))],
387                OpenpgpSettings::new(
388                    NumDataSignatures::new(NonZeroUsize::new(2).expect("2 is larger than 0")),
389                    VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
390                )?
391            ),
392            os_defaults: Some(&TechnologySettings::new(
393                vec![ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml"))],
394                OpenpgpSettings::new(
395                    NumDataSignatures::new(NonZeroUsize::new(3).expect("2 is larger than 0")),
396                    VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
397                )?
398            )),
399        },
400        TechnologySettings::new(
401            vec![
402                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/20-example.yaml")),
403                ConfigOrigin::Default,
404            ],
405            OpenpgpSettings::new(
406                NumDataSignatures::default(),
407                VerificationMethod::WebOfTrust(
408                    WebOfTrustMode::new(
409                        TrustAmountFlow::default(),
410                        TrustAmountPartial::default(),
411                        HashSet::new(),
412                        HashSet::new(),
413                    )
414                )
415            )?
416        )
417    )]
418    #[case::config_with_trust_anchor_openpgp_settings_overridden_by_trust_anchor_defaults(
419        ConfigTechnologySettings::new(
420            vec![ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/20-example.yaml"))],
421            Some(ConfigOpenpgpSettings::new(
422                Some(NumDataSignatures::new(NonZeroUsize::new(1).expect("1 is larger than 0"))),
423                ConfigVerificationMethod::TrustAnchor(ConfigTrustAnchorMode::new(None, Some(HashSet::new()), Some(HashSet::new()))?)
424            )?
425        )),
426        TechnologySettingsDefaults{
427            built_in_defaults: &TechnologySettings::new(
428                vec![ConfigOrigin::ConfigFile(PathBuf::from("/usr/share/voa/example.yaml"))],
429                OpenpgpSettings::new(
430                    NumDataSignatures::new(NonZeroUsize::new(2).expect("2 is larger than 0")),
431                    VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
432                )?
433            ),
434            os_defaults: Some(&TechnologySettings::new(
435                vec![ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml"))],
436                OpenpgpSettings::new(
437                    NumDataSignatures::new(NonZeroUsize::new(3).expect("2 is larger than 0")),
438                    VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
439                )?
440            )),
441        },
442        TechnologySettings::new(
443            vec![
444                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/20-example.yaml")),
445                ConfigOrigin::Default,
446            ],
447            OpenpgpSettings::new(
448                NumDataSignatures::default(),
449                VerificationMethod::TrustAnchor(
450                    TrustAnchorMode::new(NumCertifications::default(), HashSet::new(), HashSet::new())?
451                )
452            )?
453        )
454    )]
455    fn technology_settings_from_config_with_defaults_succeeds(
456        #[case] config: ConfigTechnologySettings,
457        #[case] defaults: TechnologySettingsDefaults,
458        #[case] expected: TechnologySettings,
459    ) -> TestResult {
460        let settings = TechnologySettings::from_config_with_defaults(&config, defaults)?;
461        assert_eq!(settings, expected);
462
463        Ok(())
464    }
465
466    /// Ensures that [`TechnologySettings::from_yaml_str`] fails on invalid YAML.
467    #[test]
468    fn technology_settings_from_yaml_str_fails_on_invalid_yaml() {
469        let broken_yaml = "foo:\n  bar:";
470
471        match TechnologySettings::from_yaml_str(broken_yaml) {
472            Err(Error::YamlDeserialize { .. }) => {}
473            Err(error) => {
474                panic!(
475                    "Expected an Error::YamlDeserialize but failed with a different error: {error}"
476                )
477            }
478            Ok(settings) => {
479                panic!("Expected an Error::YamlDeserialize but succeeded: {settings:?}")
480            }
481        }
482    }
483
484    #[test]
485    fn technology_settings_from_yaml_str_fails_on_validation() {
486        let invalid_settings = r#"origins:
487  - config_file: /usr/share/voa/example.yaml
488openpgp:
489  num_data_signatures: 2
490  verification_method:
491    plain:
492      identity_domain_matches:
493        - example.org
494      fingerprint_matches:
495        - d3b0f7c0b825ecbb0f0d7398072947e7b1537b6f
496"#;
497
498        match TechnologySettings::from_yaml_str(invalid_settings) {
499            Err(Error::Validation { .. }) => {}
500            Err(error) => {
501                panic!("Expected an Error::Validation but failed with a different error: {error}")
502            }
503            Ok(settings) => {
504                panic!("Expected an Error::Validation but succeeded: {settings:?}")
505            }
506        }
507    }
508
509    /// Ensures that the string representation of [`TechnologySettings`] remains consistent.
510    #[rstest]
511    #[case::plain_mode_empty_lists(
512        TechnologySettings::new(
513            vec![
514                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml")),
515                ConfigOrigin::Default,
516            ],
517            OpenpgpSettings::new(
518                NumDataSignatures::default(),
519                VerificationMethod::Plain(PlainMode::new(HashSet::new(), HashSet::new()))
520            )?
521        )
522    )]
523    #[case::plain_mode_custom_lists(
524        TechnologySettings::new(
525            vec![
526                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml")),
527                ConfigOrigin::ConfigFile(PathBuf::from("/usr/share/voa/example.yaml")),
528            ],
529            OpenpgpSettings::new(
530                NumDataSignatures::default(),
531                VerificationMethod::Plain(PlainMode::new(
532                    HashSet::from_iter(["example.org".parse()?, "sub.example.org".parse()?]),
533                    HashSet::from_iter([
534                        "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?,
535                        "e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?,
536                    ]),
537                ))
538            )?
539        )
540    )]
541    #[case::trust_anchor_empty_lists(
542        TechnologySettings::new(
543            vec![
544                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml")),
545                ConfigOrigin::Default,
546            ],
547            OpenpgpSettings::new(
548                NumDataSignatures::default(),
549                VerificationMethod::TrustAnchor(
550                    TrustAnchorMode::new(
551                        NumCertifications::default(),
552                        HashSet::new(),
553                        HashSet::new(),
554                    )?
555                )
556            )?
557        )
558    )]
559    #[case::trust_anchor_custom_lists(
560        TechnologySettings::new(
561            vec![
562                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml")),
563                ConfigOrigin::ConfigFile(PathBuf::from("/usr/share/voa/example.yaml")),
564            ],
565            OpenpgpSettings::new(
566                NumDataSignatures::default(),
567                VerificationMethod::TrustAnchor(
568                    TrustAnchorMode::new(
569                        NumCertifications::default(),
570                        HashSet::from_iter(["example.org".parse()?, "sub.example.org".parse()?]),
571                        HashSet::from_iter([
572                            "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?,
573                            "e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?,
574                            "6eadeac2dade6347e87c0d24fd455feffa7069f0".parse()?,
575                        ]),
576                    )?
577                )
578            )?
579        )
580    )]
581    #[case::wot_empty_lists(
582        TechnologySettings::new(
583            vec![
584                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml")),
585                ConfigOrigin::Default,
586            ],
587            OpenpgpSettings::new(
588                NumDataSignatures::default(),
589                VerificationMethod::WebOfTrust(
590                    WebOfTrustMode::new(
591                        TrustAmountFlow::default(),
592                        TrustAmountPartial::default(),
593                        HashSet::new(),
594                        HashSet::new(),
595                    )
596                )
597            )?
598        )
599    )]
600    #[case::wot_custom_lists(
601        TechnologySettings::new(
602            vec![
603                ConfigOrigin::DropInFile(PathBuf::from("/usr/share/voa/example.yaml.d/10-example.yaml")),
604                ConfigOrigin::ConfigFile(PathBuf::from("/usr/share/voa/example.yaml")),
605            ],
606            OpenpgpSettings::new(
607                NumDataSignatures::default(),
608                VerificationMethod::WebOfTrust(
609                    WebOfTrustMode::new(
610                        TrustAmountFlow::default(),
611                        TrustAmountPartial::default(),
612                        HashSet::from_iter([
613                            WebOfTrustRoot::new(
614                                "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?,
615                                TrustAmountRoot::default(),
616                            ),
617                            WebOfTrustRoot::new(
618                                "e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?,
619                                TrustAmountRoot::default(),
620                            ),
621                        ]),
622                        HashSet::from_iter(["example.org".parse()?, "sub.example.org".parse()?]),
623                    )
624                )
625            )?
626        )
627    )]
628    fn technology_settings_display(#[case] settings: TechnologySettings) -> TestResult {
629        let description =
630            "Technology settings with OpenPGP settings that use various custom settings.";
631        let settings_str = settings.to_string();
632
633        with_settings!({
634            description => description,
635            snapshot_path => SNAPSHOT_PATH,
636            prepend_module_to_snapshot => false,
637        }, {
638            assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), settings_str);
639        });
640
641        Ok(())
642    }
643
644    /// Ensures that the default string representation of [`VerificationMethod`] used in
645    /// [`TechnologySettings`] remains consistent.
646    #[rstest]
647    #[case::plain_mode(
648        TechnologySettings::new(
649            vec![
650                ConfigOrigin::Default,
651            ],
652            OpenpgpSettings::new(
653                NumDataSignatures::default(),
654                VerificationMethod::Plain(PlainMode::default())
655            )?
656        )
657    )]
658    #[case::trust_anchor(
659        TechnologySettings::new(
660            vec![
661                ConfigOrigin::Default,
662            ],
663            OpenpgpSettings::new(
664                NumDataSignatures::default(),
665                VerificationMethod::TrustAnchor(TrustAnchorMode::default())
666            )?
667        )
668    )]
669    #[case::wot(
670        TechnologySettings::new(
671            vec![
672                ConfigOrigin::Default,
673            ],
674            OpenpgpSettings::new(
675                NumDataSignatures::default(),
676                VerificationMethod::WebOfTrust(WebOfTrustMode::default())
677            )?
678        )
679    )]
680    fn technology_settings_verification_method_defaults(
681        #[case] settings: TechnologySettings,
682    ) -> TestResult {
683        let description = "Technology settings with OpenPGP settings that use the default settings for each verification method.";
684        let settings_str = settings.to_string();
685
686        with_settings!({
687            description => description,
688            snapshot_path => SNAPSHOT_PATH,
689            prepend_module_to_snapshot => false,
690        }, {
691            assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), settings_str);
692        });
693
694        Ok(())
695    }
696}