This commit is contained in:
2026-06-12 05:48:30 -04:00
parent 9d91ac8ebc
commit 64a5ab4b7f
37 changed files with 4530 additions and 407 deletions

View 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 == ()