1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// SPDX-FileCopyrightText: 2025 Simon BrummeR
//
// SPDX-License-Identifier: MPL-2.0
#[cfg(test)]
mod tests {
use crate::error::mocks::MockError;
use crate::{LoadError, MockConfigFormat, SaveError, ServiceConfig};
use mockall::predicate::eq;
use std::io::ErrorKind;
use std::path::PathBuf;
/// Expected behavior:
/// If an load attempt fails with an IO error (e.g. file does not exist) then
/// saving the default configuration is attempted. If this fails for any reason an error is returned.
#[test]
fn try_load_service_config_loading_and_saving_fails_return_error() {
let path = PathBuf::default();
let config_data = String::default();
let mut mock = MockConfigFormat::new();
mock.expect_try_load()
.with(eq(path.clone()))
.times(1)
.returning(|_| Err(LoadError::Io(ErrorKind::NotFound.into())));
mock.expect_try_save()
.with(eq(path.clone()), eq(config_data.clone()))
.times(1)
.returning(|_, _| Err(SaveError::Io(ErrorKind::NotFound.into())));
assert!(ServiceConfig::try_load(path.as_path(), mock).is_err());
}
/// Expected behavior:
/// If an load attempt fails due to an serialization error, then a configuration file exists
/// and with bogus content. In this case the configuration is not overwritten with a default
/// configuration because it would jeopardize users attempt to fix it. Just return an error in
/// this case.
#[test]
fn try_load_service_config_loading_fails_due_to_serialization_skip_saving() {
let path = PathBuf::default();
let mut mock = MockConfigFormat::new();
mock.expect_try_load()
.with(eq(path.clone()))
.times(1)
.returning(|_| Err(LoadError::Deserialize(MockError.into())));
assert!(ServiceConfig::try_load(path.as_path(), mock).is_err());
}
/// Expected behavior:
/// If an load attempt fails with an IO error (e.g. file does not exist) then
/// saving the default configuration is attempted. If this is successful a ServiceConfig
/// is constructed containing the default version of the underlying data type.
#[test]
fn try_load_service_config_loading_fails_and_saving_success_return_ok() {
let path = PathBuf::default();
let config_data = String::default();
let mut mock = MockConfigFormat::new();
mock.expect_try_load()
.with(eq(path.clone()))
.times(1)
.returning(|_| Err(LoadError::Io(ErrorKind::NotFound.into())));
mock.expect_try_save()
.with(eq(path.clone()), eq(config_data.clone()))
.times(1)
.returning(|_, _| Ok(()));
let config = ServiceConfig::try_load(path.as_path(), mock).unwrap();
assert_eq!(config.get(), String::default());
}
/// Expected behavior:
/// If an load is successful the deserialzed configuration object is stored and available as
/// latest configuration. Nothing is saved.
#[test]
fn try_load_service_config_loading_success() {
let path = PathBuf::default();
let config_data = "this_is_a_non_default_string";
let mut mock = MockConfigFormat::new();
mock.expect_try_load()
.with(eq(path.clone()))
.times(1)
.returning(|_| Ok(config_data.to_string()));
let config = ServiceConfig::try_load(path.as_path(), mock).unwrap();
assert_eq!(config.get(), config_data);
}
/// Expected behavior:
/// If an load attempt fails with an IO error (e.g. file does not exist) then
/// saving the default configuration is attempted. If this fails, a functional ServiceConfig is returned
/// containing the default of the configuration type.
#[test]
fn try_load_or_default_service_config_loading_and_saving_fails() {
let path = PathBuf::default();
let config_data = String::default();
let mut mock = MockConfigFormat::new();
mock.expect_try_load()
.with(eq(path.clone()))
.times(1)
.returning(|_| Err(LoadError::Io(ErrorKind::NotFound.into())));
mock.expect_try_save()
.with(eq(path.clone()), eq(config_data.clone()))
.times(1)
.returning(|_, _| Err(SaveError::Io(ErrorKind::NotFound.into())));
assert_eq!(
ServiceConfig::try_load_or_default(path.as_path(), mock).get(),
String::default()
);
}
/// Expected behavior:
/// If an load attempt fails due to an serialization error, then a configuration file exists
/// and with bogus content. In this case the configuration is not overwritten with a default
/// configuration because it would jeopardize users attempt to fix it.
/// Instead of an error try_load_or_default returns a functional ServiceConfig containing the
/// default of the configuration type.
#[test]
fn try_load_or_default_service_config_loading_fails_due_to_serialization_skip_saving() {
let path = PathBuf::default();
let mut mock = MockConfigFormat::new();
mock.expect_try_load()
.with(eq(path.clone()))
.times(1)
.returning(|_| Err(LoadError::Deserialize(MockError.into())));
assert_eq!(
ServiceConfig::try_load_or_default(path.as_path(), mock).get(),
String::default()
);
}
/// Expected behavior:
/// If an load attempt fails with an IO error (e.g. file does not exist) then
/// saving the default configuration is attempted. If this is successful a ServiceConfig
/// is constructed containing the default version of the underlying data type. The behavior is
/// equivalent with happy cases of try_load.
#[test]
fn try_load_or_default_service_config_loading_fails_and_saving_success_return_ok() {
let path = PathBuf::default();
let config_data = String::default();
let mut mock = MockConfigFormat::new();
mock.expect_try_load()
.with(eq(path.clone()))
.times(1)
.returning(|_| Err(LoadError::Io(ErrorKind::NotFound.into())));
mock.expect_try_save()
.with(eq(path.clone()), eq(config_data.clone()))
.times(1)
.returning(|_, _| Ok(()));
assert_eq!(
ServiceConfig::try_load_or_default(path.as_path(), mock).get(),
String::default()
);
}
/// Expected behavior:
/// If an load is successful the deserialzed configuration object is stored and available as
/// latest configuration. The behavior is equivalent with happy cases of try_load.
#[test]
fn try_load_or_default_service_config_loading_success() {
let path = PathBuf::default();
let config_data = "this_is_a_non_default_string";
let mut mock = MockConfigFormat::new();
mock.expect_try_load()
.with(eq(path.clone()))
.times(1)
.returning(|_| Ok(config_data.to_string()));
assert_eq!(
ServiceConfig::try_load_or_default(path.as_path(), mock).get(),
config_data
);
}
}