All 4 ACs verified, 7 tests pass, ruff clean. No issues found. Story status updated to done; no new deferred items. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.0 KiB
Story 1.2: Configuration Read/Write/Wipe
Status: done
Story
As a provisioning system,
I want a single config module that reads, writes, and wipes /etc/planemapper/config.json,
So that all components share one reliable config boundary with no direct filesystem access elsewhere.
Acceptance Criteria
-
Given no config file exists at
CONFIG_PATHWhenconfig.read()is called Then it raisesFileNotFoundError -
Given a valid config dict with home lat/lon, coverage radius, WiFi SSID/password, and
provisionedflag Whenconfig.write(data)is called Then the file is created atCONFIG_PATHwith correct JSON content and all expected keys present -
Given an existing config file When
config.wipe()is called Then the config file is deleted and a subsequentconfig.read()raisesFileNotFoundError -
Given a test using
conftest.pyWhenCONFIG_PATHis patched totmp_pathThen all config operations work without touching/etc/planemapper/
Tasks / Subtasks
-
Task 1: Implement
config.read()insrc/planemapper/provisioning/config.py(AC: #1, #4)- 1.1 Import
CONFIG_PATHfromplanemapper.constants - 1.2 Implement
read() -> dictthat opensCONFIG_PATHand parses JSON - 1.3 Let
FileNotFoundErrorpropagate naturally if the file does not exist — no bareexcept
- 1.1 Import
-
Task 2: Implement
config.write(data)insrc/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_PATHwithindent=2
- 2.1 Implement
-
Task 3: Implement
config.wipe()insrc/planemapper/provisioning/config.py(AC: #3, #4)- 3.1 Implement
wipe() -> Nonethat deletesCONFIG_PATHif it exists - 3.2 Use
CONFIG_PATH.unlink(missing_ok=True)so wipe is idempotent
- 3.1 Implement
-
Task 4: Update
tests/conftest.pywithCONFIG_PATHpatch fixture (AC: #4)- 4.1 Add a
monkeypatchfixture (or autouse session fixture) that patchesplanemapper.provisioning.config.CONFIG_PATHtotmp_path / "config.json"for every test - 4.2 Confirm the fixture is applied so no test ever touches
/etc/planemapper/
- 4.1 Add a
-
Task 5: Write tests in
tests/provisioning/test_config.pycovering all 4 ACs (AC: #1, #2, #3, #4)- 5.1 Test AC1: call
config.read()with no file present; assertFileNotFoundErroris 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 assertconfig.read()raisesFileNotFoundError - 5.4 Test AC4: confirm
CONFIG_PATHresolves insidetmp_path(not/etc/planemapper/) during test execution
- 5.1 Test AC1: call
-
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
- 6.1
Dev Notes
CONFIG_PATH patching
CONFIG_PATH is a module-level Path constant defined in planemapper.constants and imported into planemapper.provisioning.config. Patch the name in the config module's namespace — monkeypatch.setattr("planemapper.provisioning.config.CONFIG_PATH", tmp_path / "config.json") — not in planemapper.constants, so that all three functions pick up the patched value.
Directory creation in config.write()
The directory /etc/planemapper/ will not exist in CI or on a fresh OS install. config.write() must call CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) before opening the file for writing. In tests, tmp_path already exists, so this call is a safe no-op.
JSON formatting
Write config files with json.dump(data, f, indent=2) for human readability. No extra dependencies — use the Python stdlib json module only.
Error handling policy
Do not catch any exceptions inside read(), write(), or wipe(). Let FileNotFoundError, PermissionError, json.JSONDecodeError, etc. propagate to callers. Provisioning code higher up the stack is responsible for user-facing error messages.
Config schema
The canonical key set written by config.write() and expected by all consumers:
home_lat float — decimal degrees, WGS-84
home_lon float — decimal degrees, WGS-84
coverage_radius_nm int — display radius in nautical miles
wifi_ssid str — WiFi network name
wifi_password str — WiFi passphrase
provisioned bool — True once provisioning has completed
File location constants
CONFIG_PATH = Path("/etc/planemapper/config.json") is defined in src/planemapper/constants.py. Do not hard-code the path string anywhere in config.py or test files; always reference the module-level CONFIG_PATH symbol so the monkeypatch can redirect it cleanly.