forked from equinor/fmu-settings
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_project_config.py
More file actions
331 lines (258 loc) · 11.5 KB
/
Copy pathtest_project_config.py
File metadata and controls
331 lines (258 loc) · 11.5 KB
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
"""Tests for fmu.resources.config."""
import json
from datetime import datetime
from pathlib import Path
from typing import Any
import pytest
from fmu.datamodels.fmu_results.fields import Model, Smda
from fmu.settings._fmu_dir import ProjectFMUDirectory, UserFMUDirectory
from fmu.settings.models.project_config import ProjectConfig
from fmu.settings.models.user_config import UserConfig
from fmu.settings.resources.config_managers import (
ProjectConfigManager,
UserConfigManager,
)
@pytest.fixture
def nested_dict() -> dict[str, Any]:
"""A nested dictionary to test get-set dot notation."""
return {
"a": 1,
"b": {
"c": "2",
"d": {"e": 3},
},
}
def test_config_resource_manager(fmu_dir: ProjectFMUDirectory) -> None:
"""Tests basic facts about the ProjectConfig resource manager."""
# This manager already exists in 'fmu_dir', but just to
# try from scratch.
manager = ProjectConfigManager(fmu_dir)
assert manager.fmu_dir == fmu_dir
assert manager.model_class == ProjectConfig
assert manager._cache is None
# Resource manager requires this to be implemented
assert manager.relative_path == Path("config.json")
assert manager.path == fmu_dir.path / manager.relative_path
def test_user_config_resource_manager(user_fmu_dir: UserFMUDirectory) -> None:
"""Tests basic facts about the ProjectConfig resource manager."""
# This manager already exists in 'fmu_dir', but just to
# try from scratch.
manager = UserConfigManager(user_fmu_dir)
assert manager.fmu_dir == user_fmu_dir
assert manager.model_class == UserConfig
assert manager._cache is None
# Resource manager requires this to be implemented
assert manager.relative_path == Path("config.json")
assert manager.path == user_fmu_dir.path / manager.relative_path
def test_load_config(
fmu_dir: ProjectFMUDirectory,
config_model: ProjectConfig,
) -> None:
"""Tests that load() works when a configuration exists."""
assert fmu_dir.config.load() == config_model
assert fmu_dir.config._cache == config_model
def test_load_user_config(
user_fmu_dir: UserFMUDirectory,
user_config_model: UserConfig,
) -> None:
"""Tests that load() works when a user configuration exists."""
assert user_fmu_dir.config.load() == user_config_model
assert user_fmu_dir.config._cache == user_config_model
def test_get_config_missing_file(tmp_path: Path) -> None:
"""Tests get_config raises FileNotFoundError when config.json missing."""
empty_fmu_dir = tmp_path / ".fmu"
empty_fmu_dir.mkdir()
fmu_dir = ProjectFMUDirectory(tmp_path)
assert fmu_dir.config.exists is False
with pytest.raises(
FileNotFoundError,
match=(
"Resource file for 'ProjectConfigManager' not found at: "
f"'{empty_fmu_dir}/config.json'"
),
):
fmu_dir.config.load()
def test_get_config_invalid_json(fmu_dir: ProjectFMUDirectory) -> None:
"""Tests a corrupted config.json raises ValueError."""
config_path = fmu_dir.path / "config.json"
with open(config_path, "a", encoding="utf-8") as f:
f.write("%")
with pytest.raises(
ValueError, match="Invalid JSON in resource file for 'ProjectConfigManager'"
):
fmu_dir.config.load(force=True)
def test_get_dot_notation_key(
nested_dict: dict[str, Any], fmu_dir: ProjectFMUDirectory
) -> None:
"""Tests the get helper function for dot notation works as expected."""
assert fmu_dir.config._get_dot_notation_key(nested_dict, "b.c") == "2"
assert fmu_dir.config._get_dot_notation_key(nested_dict, "b.d") == {"e": 3}
assert fmu_dir.config._get_dot_notation_key(nested_dict, "b.d.e") == 3 # noqa PLR2004
assert fmu_dir.config._get_dot_notation_key(nested_dict, "b.z") is None
assert fmu_dir.config._get_dot_notation_key(nested_dict, "b.z", "foo") == "foo"
def test_get_key(fmu_dir: ProjectFMUDirectory) -> None:
"""Tests getting a key on configuration."""
assert fmu_dir.config.get("created_by") == "user"
# No nested entries exist in config yet.
assert fmu_dir.config.get("does.not.exist", "foo") == "foo"
assert fmu_dir.config.get("notreal", "foo") == "foo"
def test_get_key_config_does_not_exist(tmp_path: Path) -> None:
"""Tests getting a key when the config is missing."""
empty_fmu_dir = tmp_path / ".fmu"
empty_fmu_dir.mkdir()
fmu_dir = ProjectFMUDirectory(tmp_path)
assert fmu_dir.config.exists is False
with pytest.raises(
FileNotFoundError,
match=(
"Resource file for 'ProjectConfigManager' not found at: "
f"'{empty_fmu_dir}/config.json'"
),
):
fmu_dir.config.get("version")
def test_set_dot_notation_key(
nested_dict: dict[str, Any], fmu_dir: ProjectFMUDirectory
) -> None:
"""Tests the set helper function for dot notation works as expected."""
assert nested_dict["b"]["c"] == "2"
fmu_dir.config._set_dot_notation_key(nested_dict, "b.c", "3")
assert nested_dict["b"]["c"] == "3"
assert nested_dict["b"]["d"] == {"e": 3}
fmu_dir.config._set_dot_notation_key(nested_dict, "b.d", {"e": "s"})
assert nested_dict["b"]["d"] == {"e": "s"}
fmu_dir.config._set_dot_notation_key(nested_dict, "b.d.e", 3)
assert nested_dict["b"]["d"] == {"e": 3}
assert fmu_dir.config._get_dot_notation_key(nested_dict, "b.z") is None
fmu_dir.config._set_dot_notation_key(nested_dict, "b.z", "foo")
assert nested_dict["b"]["z"] == "foo"
# Overwrites existing non-dict values
fmu_dir.config._set_dot_notation_key(nested_dict, "a.b", {})
assert nested_dict["a"] != 1
assert nested_dict["a"]["b"] == {}
def test_set_key(fmu_dir: ProjectFMUDirectory) -> None:
"""Tests setting a key on configuration."""
fmu_dir.config.set("created_by", "user2")
assert fmu_dir.config.get("created_by") == "user2"
config_model = fmu_dir.config.load(force=True)
config_dict = config_model.model_dump()
assert config_dict["created_by"] == "user2"
# No nested entries exist in config yet.
fmu_dir.config.set("does.not.exist", "foo")
assert fmu_dir.config.get("does.not.exist") is None
fmu_dir.config.set("notreal", "foo")
assert fmu_dir.config.get("notreal") is None
# Does not write invalid values
config_model = fmu_dir.config.load(force=True)
config_dict = config_model.model_dump()
assert config_dict.get("does", None) is None
assert config_dict.get("notreal", None) is None
with pytest.raises(
ValueError,
match=(
"Invalid value set for 'ProjectConfigManager' with key 'version', "
"value '2.0'"
),
):
fmu_dir.config.set("version", 2.0)
def test_set_key_config_does_not_exist(tmp_path: Path) -> None:
"""Tests getting a key when the config is missing."""
empty_fmu_dir = tmp_path / ".fmu"
empty_fmu_dir.mkdir()
fmu_dir = ProjectFMUDirectory(tmp_path)
assert fmu_dir.config.exists is False
with pytest.raises(
FileNotFoundError,
match=(
"Resource file for 'ProjectConfigManager' not found at: "
f"'{empty_fmu_dir}/config.json'"
),
):
fmu_dir.config.set("version", "200.0.0")
def test_update_config(fmu_dir: ProjectFMUDirectory) -> None:
"""Tests setting a key on configuration."""
fmu_dir.config.update({"created_by": "user2", "version": "200.0.0", "not.real": 0})
assert fmu_dir.config.get("created_by") == "user2"
assert fmu_dir.config.get("version") == "200.0.0"
assert fmu_dir.config.get("not") is None
bad_updates = {"created_by": {}, "version": "major"}
with pytest.raises(
ValueError,
match="Invalid value set for 'ProjectConfigManager' with updates "
f"'{bad_updates}'",
):
fmu_dir.config.update(bad_updates)
def test_set_smda(
fmu_dir: ProjectFMUDirectory, masterdata_dict: dict[str, Any]
) -> None:
"""Tests setting the masterdata.smda value in the config."""
assert fmu_dir.config.get("masterdata.smda") is None
with open(fmu_dir.path / fmu_dir.config.relative_path, encoding="utf-8") as f:
config_on_disk = json.loads(f.read())
assert config_on_disk["masterdata"] is None
fmu_dir.set_config_value("masterdata", masterdata_dict)
smda_model = Smda.model_validate(masterdata_dict["smda"])
# Compare to round-trip'd Pydantic model for uuid-str to UUID() obj conversion
assert fmu_dir.get_config_value("masterdata.smda") == smda_model.model_dump()
with open(fmu_dir.path / fmu_dir.config.relative_path, encoding="utf-8") as f:
config_on_disk = json.loads(f.read())
config_on_disk_model = ProjectConfig.model_validate(config_on_disk)
assert fmu_dir.config._cache is not None
assert fmu_dir.config._cache.masterdata is not None
assert fmu_dir.config._cache.masterdata.smda == smda_model
assert config_on_disk_model == fmu_dir.config._cache
def test_set_model_invalid_fails(
fmu_dir: ProjectFMUDirectory, model_dict: dict[str, Any]
) -> None:
"""Tests setting the model value in the config using an invalid dictionary."""
assert fmu_dir.config.get("model") is None
# drop model.name to test validation
model_dict.pop("name")
with pytest.raises(ValueError, match="model.name"):
fmu_dir.set_config_value("model", model_dict)
def test_set_model(fmu_dir: ProjectFMUDirectory, model_dict: dict[str, Any]) -> None:
"""Tests setting the model value in the config."""
assert fmu_dir.config.get("model") is None
with open(fmu_dir.path / fmu_dir.config.relative_path, encoding="utf-8") as f:
config_on_disk = json.loads(f.read())
assert config_on_disk["model"] is None
fmu_dir.set_config_value("model", model_dict)
model = Model.model_validate(model_dict)
assert fmu_dir.get_config_value("model") == model
assert fmu_dir.get_config_value("model.revision") == "21.0.0"
assert fmu_dir.get_config_value("model.name") == "Drogon"
with open(fmu_dir.path / fmu_dir.config.relative_path, encoding="utf-8") as f:
config_on_disk = json.loads(f.read())
config_on_disk_model = ProjectConfig.model_validate(config_on_disk)
assert fmu_dir.config._cache is not None
assert fmu_dir.config._cache.model == model
assert config_on_disk_model == fmu_dir.config._cache
def test_update_config_when_it_does_not_exist(tmp_path: Path) -> None:
"""Tests getting a key when the config is missing."""
empty_fmu_dir = tmp_path / ".fmu"
empty_fmu_dir.mkdir()
fmu_dir = ProjectFMUDirectory(tmp_path)
assert fmu_dir.config.exists is False
with pytest.raises(
FileNotFoundError,
match=(
"Resource file for 'ProjectConfigManager' not found at: "
f"'{empty_fmu_dir}/config.json'"
),
):
fmu_dir.config.update({"created_by": "user", "version": "200.0.0"})
def test_save(fmu_dir: ProjectFMUDirectory, config_dict: dict[str, Any]) -> None:
"""Tests that save functions as expected."""
config = fmu_dir.config.load()
assert fmu_dir.config._cache == config
assert config.created_by == "user"
config_dict["created_by"] = "user2"
new_config = ProjectConfig.model_validate(config_dict)
fmu_dir.config.save(new_config)
assert fmu_dir.config._cache == new_config
with open(fmu_dir.config.path, encoding="utf-8") as f:
new_config_dict = json.loads(f.read())
# Patch the raw datetime string from json
new_config_dict["created_at"] = datetime.fromisoformat(
new_config_dict["created_at"]
)
assert config_dict == new_config_dict