Background
Follow-up to #2192 (foundation) and PR #2193 (pytest + Zuul infrastructure). osism/settings.py reads configuration from environment variables and from Docker secrets in /run/secrets/. Defaults are important here because they determine behavior when an operator forgets to set a variable — so they deserve a regression test.
Scope
Add tests/unit/test_settings.py covering the read_secret helper and the module-level settings in osism/settings.py.
Test targets
read_secret(secret_name) — osism/settings.py:6
- Existing file under
/run/secrets/<name> with content \"abc\\n\" → returns \"abc\" (trailing newline stripped)
- File does not exist → returns
\"\" (not None)
- File is empty → returns
\"\"
- File has leading/trailing whitespace on the first line → only trailing is stripped via
.strip() (verify exact behavior matches implementation)
Use tmp_path and monkeypatch to redirect the /run/secrets/ path — the cleanest approach is monkeypatch.setattr(\"builtins.open\", ...) or refactor-free: pass a tmp_path-based path via monkeypatch.setattr of a helper. If that proves intrusive, it is acceptable to test by actually creating /run/secrets/ is not acceptable — use mocking instead.
Module-level settings
Because settings are read at import time, tests need to re-import osism.settings under controlled environments. The pattern:
import importlib
import osism.settings as settings_module
def test_default_redis_host(monkeypatch):
monkeypatch.delenv(\"REDIS_HOST\", raising=False)
importlib.reload(settings_module)
assert settings_module.REDIS_HOST == \"redis\"
Required cases (pick representatives, not every single var):
| Variable |
Default |
Override test |
REDIS_HOST |
\"redis\" |
REDIS_HOST=mycache → \"mycache\" |
REDIS_PORT |
6379 (int) |
REDIS_PORT=1234 → 1234 (int) |
REDIS_DB |
0 (int) |
REDIS_DB=5 → 5 |
IGNORE_SSL_ERRORS |
True |
IGNORE_SSL_ERRORS=False → False; any other string → False |
GATHER_FACTS_SCHEDULE |
43200.0 (float) |
GATHER_FACTS_SCHEDULE=60 → 60.0 |
FACTS_MAX_AGE |
equals int of GATHER_FACTS_SCHEDULE when unset |
explicit override honored |
INVENTORY_RECONCILER_SCHEDULE |
600.0 |
override honored |
OPERATOR_USER |
\"dragon\" |
OSISM_OPERATOR_USER=admin → \"admin\" |
FRR_DUMMY_INTERFACE |
\"loopback0\" |
OSISM_FRR_DUMMY_INTERFACE=lo1 → \"lo1\" |
NETBOX_URL |
None |
NETBOX_API beats NETBOX_URL when both set (verify precedence in os.getenv(\"NETBOX_API\", os.getenv(\"NETBOX_URL\"))) |
NETBOX_TOKEN |
\"\" |
env var wins over secret file; falls back to read_secret when env is unset; .strip() applied |
SONIC_EXPORT_DIR / SONIC_EXPORT_PREFIX / SONIC_EXPORT_SUFFIX / SONIC_EXPORT_IDENTIFIER |
string defaults |
override honored |
NETBOX_SECONDARIES |
\"[]\" |
env wins; read_secret fallback; literal default when both absent |
REDFISH_TIMEOUT |
20 (int) |
override honored |
NETBOX_MAX_CONNECTIONS |
5 (int) |
override honored |
NETBOX_FILTER_CONDUCTOR_IRONIC / NETBOX_FILTER_CONDUCTOR_SONIC
- Defaults are the literal strings documented in
settings.py:44-51
- Override via env is honored
Mocking hints
- Use
monkeypatch.setenv / monkeypatch.delenv and importlib.reload(osism.settings) per test. Restore the module at the end of the test module via an autouse fixture so later tests see the original state.
- For
read_secret, mock the open(...) call or monkeypatch a path constant if you extract one; do not write to /run/secrets/ on the host.
Definition of Done
Dependencies
Background
Follow-up to #2192 (foundation) and PR #2193 (pytest + Zuul infrastructure).
osism/settings.pyreads configuration from environment variables and from Docker secrets in/run/secrets/. Defaults are important here because they determine behavior when an operator forgets to set a variable — so they deserve a regression test.Scope
Add
tests/unit/test_settings.pycovering theread_secrethelper and the module-level settings inosism/settings.py.Test targets
read_secret(secret_name)—osism/settings.py:6/run/secrets/<name>with content\"abc\\n\"→ returns\"abc\"(trailing newline stripped)\"\"(notNone)\"\".strip()(verify exact behavior matches implementation)Use
tmp_pathandmonkeypatchto redirect the/run/secrets/path — the cleanest approach ismonkeypatch.setattr(\"builtins.open\", ...)or refactor-free: pass atmp_path-based path viamonkeypatch.setattrof a helper. If that proves intrusive, it is acceptable to test by actually creating/run/secrets/is not acceptable — use mocking instead.Module-level settings
Because settings are read at import time, tests need to re-import
osism.settingsunder controlled environments. The pattern:Required cases (pick representatives, not every single var):
REDIS_HOST\"redis\"REDIS_HOST=mycache→\"mycache\"REDIS_PORT6379(int)REDIS_PORT=1234→1234(int)REDIS_DB0(int)REDIS_DB=5→5IGNORE_SSL_ERRORSTrueIGNORE_SSL_ERRORS=False→False; any other string →FalseGATHER_FACTS_SCHEDULE43200.0(float)GATHER_FACTS_SCHEDULE=60→60.0FACTS_MAX_AGEGATHER_FACTS_SCHEDULEwhen unsetINVENTORY_RECONCILER_SCHEDULE600.0OPERATOR_USER\"dragon\"OSISM_OPERATOR_USER=admin→\"admin\"FRR_DUMMY_INTERFACE\"loopback0\"OSISM_FRR_DUMMY_INTERFACE=lo1→\"lo1\"NETBOX_URLNoneNETBOX_APIbeatsNETBOX_URLwhen both set (verify precedence inos.getenv(\"NETBOX_API\", os.getenv(\"NETBOX_URL\")))NETBOX_TOKEN\"\"read_secretwhen env is unset;.strip()appliedSONIC_EXPORT_DIR/SONIC_EXPORT_PREFIX/SONIC_EXPORT_SUFFIX/SONIC_EXPORT_IDENTIFIERNETBOX_SECONDARIES\"[]\"read_secretfallback; literal default when both absentREDFISH_TIMEOUT20(int)NETBOX_MAX_CONNECTIONS5(int)NETBOX_FILTER_CONDUCTOR_IRONIC/NETBOX_FILTER_CONDUCTOR_SONICsettings.py:44-51Mocking hints
monkeypatch.setenv/monkeypatch.delenvandimportlib.reload(osism.settings)per test. Restore the module at the end of the test module via an autouse fixture so later tests see the original state.read_secret, mock theopen(...)call ormonkeypatcha path constant if you extract one; do not write to/run/secrets/on the host.Definition of Done
tests/unit/test_settings.pycreatedread_secrethas tests for happy path, missing file, and whitespace handlingpipenv run pytest tests/unit/test_settings.pypasses locallypipenv run pyteston the whole suite)flake8,mypy,python-blackremain greenpython-osism-unit-testspassesDependencies