Implement story 1.2: config read/write/wipe

Add provisioning/config.py with read/write/wipe functions, update
conftest.py with autouse CONFIG_PATH patch fixture, write 7 tests
covering all acceptance criteria, and extend pyproject.toml
per-file-ignores so conftest.py may import from provisioning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Edholm
2026-04-22 22:35:11 -04:00
parent 8682b8714e
commit 826f1d98fa
6 changed files with 122 additions and 31 deletions
@@ -1,6 +1,6 @@
# Story 1.2: Configuration Read/Write/Wipe
Status: ready-for-dev
Status: review
## Story
@@ -20,34 +20,34 @@ So that all components share one reliable config boundary with no direct filesys
## Tasks / Subtasks
- [ ] Task 1: Implement `config.read()` in `src/planemapper/provisioning/config.py` (AC: #1, #4)
- [ ] 1.1 Import `CONFIG_PATH` from `planemapper.constants`
- [ ] 1.2 Implement `read() -> dict` that opens `CONFIG_PATH` and parses JSON
- [ ] 1.3 Let `FileNotFoundError` propagate naturally if the file does not exist — no bare `except`
- [x] Task 1: Implement `config.read()` in `src/planemapper/provisioning/config.py` (AC: #1, #4)
- [x] 1.1 Import `CONFIG_PATH` from `planemapper.constants`
- [x] 1.2 Implement `read() -> dict` that opens `CONFIG_PATH` and parses JSON
- [x] 1.3 Let `FileNotFoundError` propagate naturally if the file does not exist — no bare `except`
- [ ] Task 2: Implement `config.write(data)` in `src/planemapper/provisioning/config.py` (AC: #2, #4)
- [ ] 2.1 Implement `write(data: dict) -> None`
- [ ] 2.2 Call `CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)` before writing to ensure `/etc/planemapper/` exists
- [ ] 2.3 Write JSON to `CONFIG_PATH` with `indent=2`
- [x] Task 2: Implement `config.write(data)` in `src/planemapper/provisioning/config.py` (AC: #2, #4)
- [x] 2.1 Implement `write(data: dict) -> None`
- [x] 2.2 Call `CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)` before writing to ensure `/etc/planemapper/` exists
- [x] 2.3 Write JSON to `CONFIG_PATH` with `indent=2`
- [ ] Task 3: Implement `config.wipe()` in `src/planemapper/provisioning/config.py` (AC: #3, #4)
- [ ] 3.1 Implement `wipe() -> None` that deletes `CONFIG_PATH` if it exists
- [ ] 3.2 Use `CONFIG_PATH.unlink(missing_ok=True)` so wipe is idempotent
- [x] Task 3: Implement `config.wipe()` in `src/planemapper/provisioning/config.py` (AC: #3, #4)
- [x] 3.1 Implement `wipe() -> None` that deletes `CONFIG_PATH` if it exists
- [x] 3.2 Use `CONFIG_PATH.unlink(missing_ok=True)` so wipe is idempotent
- [ ] Task 4: Update `tests/conftest.py` with `CONFIG_PATH` patch fixture (AC: #4)
- [ ] 4.1 Add a `monkeypatch` fixture (or autouse session fixture) that patches `planemapper.provisioning.config.CONFIG_PATH` to `tmp_path / "config.json"` for every test
- [ ] 4.2 Confirm the fixture is applied so no test ever touches `/etc/planemapper/`
- [x] Task 4: Update `tests/conftest.py` with `CONFIG_PATH` patch fixture (AC: #4)
- [x] 4.1 Add a `monkeypatch` fixture (or autouse session fixture) that patches `planemapper.provisioning.config.CONFIG_PATH` to `tmp_path / "config.json"` for every test
- [x] 4.2 Confirm the fixture is applied so no test ever touches `/etc/planemapper/`
- [ ] Task 5: Write tests in `tests/provisioning/test_config.py` covering all 4 ACs (AC: #1, #2, #3, #4)
- [ ] 5.1 Test AC1: call `config.read()` with no file present; assert `FileNotFoundError` is raised
- [ ] 5.2 Test AC2: call `config.write(data)` with a valid dict; assert file exists and JSON round-trips correctly with all expected keys (`home_lat`, `home_lon`, `coverage_radius_nm`, `wifi_ssid`, `wifi_password`, `provisioned`)
- [ ] 5.3 Test AC3: write a config, call `config.wipe()`, assert file is gone, then assert `config.read()` raises `FileNotFoundError`
- [ ] 5.4 Test AC4: confirm `CONFIG_PATH` resolves inside `tmp_path` (not `/etc/planemapper/`) during test execution
- [x] Task 5: Write tests in `tests/provisioning/test_config.py` covering all 4 ACs (AC: #1, #2, #3, #4)
- [x] 5.1 Test AC1: call `config.read()` with no file present; assert `FileNotFoundError` is raised
- [x] 5.2 Test AC2: call `config.write(data)` with a valid dict; assert file exists and JSON round-trips correctly with all expected keys (`home_lat`, `home_lon`, `coverage_radius_nm`, `wifi_ssid`, `wifi_password`, `provisioned`)
- [x] 5.3 Test AC3: write a config, call `config.wipe()`, assert file is gone, then assert `config.read()` raises `FileNotFoundError`
- [x] 5.4 Test AC4: confirm `CONFIG_PATH` resolves inside `tmp_path` (not `/etc/planemapper/`) during test execution
- [ ] Task 6: Run quality gates
- [ ] 6.1 `pytest tests/` — all tests pass, 0 failures
- [ ] 6.2 `ruff check .` — zero violations
- [ ] 6.3 `ruff format --check .` — no formatting issues
- [x] Task 6: Run quality gates
- [x] 6.1 `pytest tests/` — all tests pass, 0 failures
- [x] 6.2 `ruff check .` — zero violations
- [x] 6.3 `ruff format --check .` — no formatting issues
## Dev Notes