Files
planeMapper/_bmad-output/implementation-artifacts/1-2-configuration-read-write-wipe.md
T
Matt Edholm 8682b8714e Create story 1.2: configuration read/write/wipe
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:33:13 -04:00

5.0 KiB

Story 1.2: Configuration Read/Write/Wipe

Status: ready-for-dev

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

  1. Given no config file exists at CONFIG_PATH When config.read() is called Then it raises FileNotFoundError

  2. Given a valid config dict with home lat/lon, coverage radius, WiFi SSID/password, and provisioned flag When config.write(data) is called Then the file is created at CONFIG_PATH with correct JSON content and all expected keys present

  3. Given an existing config file When config.wipe() is called Then the config file is deleted and a subsequent config.read() raises FileNotFoundError

  4. Given a test using conftest.py When CONFIG_PATH is patched to tmp_path Then all config operations work without touching /etc/planemapper/

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
  • 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
  • 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
  • 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/
  • 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
  • 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

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 namespacemonkeypatch.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.