saving
This commit is contained in:
105
tests/unit/test_config_writer.py
Normal file
105
tests/unit/test_config_writer.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""Tests for `write_config` — TOML serialiser used by the first-run wizard.
|
||||
|
||||
The load-bearing invariant: write_config → load_config produces an equivalent
|
||||
Config. If that holds, the wizard can write whatever the user picked and
|
||||
trust the rest of the codebase to read it back the same way.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from openrun.config import (
|
||||
BanisterParams,
|
||||
Config,
|
||||
HRZones,
|
||||
UserProfile,
|
||||
load_config,
|
||||
write_config,
|
||||
)
|
||||
|
||||
|
||||
def _sample_config(tmp_path: Path) -> Config:
|
||||
return Config(
|
||||
user=UserProfile(
|
||||
name="Test Runner",
|
||||
height_cm=180.0,
|
||||
weight_kg=72.5,
|
||||
hr_max=200,
|
||||
lthr=178,
|
||||
resting_hr=48,
|
||||
zones=HRZones(
|
||||
z1=(100, 120), z2=(121, 140), z3=(141, 160),
|
||||
z4=(161, 180), z5=(181, 200),
|
||||
),
|
||||
),
|
||||
db_path=tmp_path / "data" / "garmin.db",
|
||||
banister=BanisterParams(ctl_tau_days=42.0, atl_tau_days=7.0, race_day_tl_per_km=7.0),
|
||||
races=(("wk 4 — 30K", "2026-06-13"), ("wk 17 — 50 mile", "2026-09-12")),
|
||||
)
|
||||
|
||||
|
||||
def test_write_then_load_roundtrip(tmp_path: Path) -> None:
|
||||
cfg = _sample_config(tmp_path)
|
||||
out = tmp_path / "openrun.toml"
|
||||
write_config(cfg, out)
|
||||
|
||||
loaded = load_config(out)
|
||||
assert loaded.user.name == cfg.user.name
|
||||
assert loaded.user.hr_max == cfg.user.hr_max
|
||||
assert loaded.user.lthr == cfg.user.lthr
|
||||
assert loaded.user.resting_hr == cfg.user.resting_hr
|
||||
assert loaded.user.weight_kg == cfg.user.weight_kg
|
||||
assert loaded.user.height_cm == cfg.user.height_cm
|
||||
assert loaded.user.zones == cfg.user.zones
|
||||
assert loaded.banister == cfg.banister
|
||||
assert loaded.races == cfg.races
|
||||
|
||||
|
||||
def test_write_handles_missing_optional_fields(tmp_path: Path) -> None:
|
||||
"""Height/weight/lthr/rhr are optional — omitted entirely from output."""
|
||||
cfg = Config(
|
||||
user=UserProfile(name="Minimal", hr_max=190),
|
||||
db_path=tmp_path / "minimal.db",
|
||||
)
|
||||
out = tmp_path / "openrun.toml"
|
||||
write_config(cfg, out)
|
||||
body = out.read_text()
|
||||
assert "height_cm" not in body
|
||||
assert "weight_kg" not in body
|
||||
assert "lthr" not in body
|
||||
assert "resting_hr" not in body
|
||||
|
||||
loaded = load_config(out)
|
||||
assert loaded.user.hr_max == 190
|
||||
assert loaded.user.height_cm is None
|
||||
|
||||
|
||||
def test_write_quotes_strings_with_special_chars(tmp_path: Path) -> None:
|
||||
"""Race labels and athlete names commonly contain Unicode em-dashes and quotes;
|
||||
the serialiser must escape these so the file parses back."""
|
||||
cfg = Config(
|
||||
user=UserProfile(name='O\'Brien — "Speedy"', hr_max=200),
|
||||
db_path=tmp_path / "x.db",
|
||||
races=(('wk 17 — Cascade Crest "50M"', "2026-09-12"),),
|
||||
)
|
||||
out = tmp_path / "openrun.toml"
|
||||
write_config(cfg, out)
|
||||
loaded = load_config(out)
|
||||
assert loaded.user.name == 'O\'Brien — "Speedy"'
|
||||
assert loaded.races == (('wk 17 — Cascade Crest "50M"', "2026-09-12"),)
|
||||
|
||||
|
||||
def test_write_creates_parent_dir(tmp_path: Path) -> None:
|
||||
cfg = Config(user=UserProfile(name="x", hr_max=200), db_path=tmp_path / "x.db")
|
||||
nested = tmp_path / "a" / "b" / "openrun.toml"
|
||||
write_config(cfg, nested)
|
||||
assert nested.exists()
|
||||
|
||||
|
||||
def test_no_races_omits_section(tmp_path: Path) -> None:
|
||||
cfg = Config(user=UserProfile(name="x", hr_max=200), db_path=tmp_path / "x.db", races=())
|
||||
out = tmp_path / "openrun.toml"
|
||||
write_config(cfg, out)
|
||||
assert "[[races]]" not in out.read_text()
|
||||
assert load_config(out).races == ()
|
||||
Reference in New Issue
Block a user