Context ------- This is the Phase 0.5 deliverable — the hardest-to-validate unknown in the plan. Wealthfolio auth is JWT HttpOnly cookie with a 5-req/min login rate limit. CronJob pods are ephemeral, so we persist cookies to disk between runs (shared PVC in production). Plan stress-test also flagged: use the CSV import path, not per-row JSON POST. Wealthfolio's UI uses /activities/import and its dedup logic is battle-tested; CSVs double as audit artefacts we can replay. This change ----------- - WealthfolioSink (httpx async): login with username/password, persists cookie dict to session_path on disk, attaches it as a Cookie header on subsequent calls. - 401 on a non-login endpoint triggers a single re-login + retry. - ensure_account() is idempotent — GETs the account list first, only POSTs /accounts if id is missing. - import_activities() always runs /activities/import/check first; any non-2xx there raises ImportValidationError and we never touch the real import endpoint. Protects against half-written state when the broker emits a symbol Wealthfolio doesn't know. - httpx.MockTransport-based tests cover: login persistence, 401 on login raises UnauthorizedError, session reuse from disk, 401 retry path, ensure_account idempotency + creation, import dry-run-then-real sequencing, halt on check failure. Not yet covered (deferred): - Multi-process file lock on session_path (single-process enough for now; Phase 1 adds it when multiple CronJobs run concurrently). - 429 jittered backoff (TBD when Wealthfolio actually rate-limits us). Test plan --------- ## Automated - poetry run pytest -q → 31 passed - poetry run mypy broker_sync tests → Success: no issues found in 17 source files - poetry run ruff check . → All checks passed! ## Manual Verification Live auth spike against https://wealthfolio.viktorbarzin.me deferred until the password is seeded into Vault at secret/broker-sync/wealthfolio in a follow-up commit (needs Viktor's Vault session).
0 lines
Python
0 lines
Python