diff --git a/Dockerfile b/Dockerfile index 541b67d..a6c526c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,19 @@ FROM python:3.12-slim AS builder ENV POETRY_VERSION=1.8.4 \ - POETRY_HOME=/opt/poetry \ POETRY_VIRTUALENVS_IN_PROJECT=true \ PIP_NO_CACHE_DIR=1 +# `pip install` puts poetry on PATH (/usr/local/bin/poetry) — don't bother +# with POETRY_HOME indirection. RUN pip install --no-cache-dir "poetry==${POETRY_VERSION}" WORKDIR /app COPY pyproject.toml poetry.lock ./ -RUN /opt/poetry/bin/poetry install --only main --no-root +RUN poetry install --only main --no-root COPY broker_sync ./broker_sync -RUN /opt/poetry/bin/poetry install --only main +RUN poetry install --only main FROM python:3.12-slim diff --git a/broker_sync/sinks/wealthfolio.py b/broker_sync/sinks/wealthfolio.py index 42bd0b0..426ec52 100644 --- a/broker_sync/sinks/wealthfolio.py +++ b/broker_sync/sinks/wealthfolio.py @@ -80,17 +80,18 @@ class WealthfolioSink: self._session_path.write_text(json.dumps({"cookies": cookies})) async def login(self) -> None: + # Wealthfolio 3.2's LoginRequest is `{ password: String }` only — a + # username key is rejected as an unknown field (HTTP 400). The + # `username` constructor arg is kept for a future Wealthfolio + # release that may add multi-user support. resp = await self._client.post( _LOGIN_PATH, - json={ - "username": self._username, - "password": self._password - }, + json={"password": self._password}, ) if resp.status_code == 401: raise WealthfolioUnauthorizedError("Wealthfolio /auth/login returned 401") resp.raise_for_status() - cookies = {k: v for k, v in resp.cookies.items()} + cookies = dict(resp.cookies.items()) if not cookies: raise WealthfolioError("/auth/login returned 2xx but no Set-Cookie") self._save_cookies(cookies) diff --git a/tests/sinks/test_wealthfolio.py b/tests/sinks/test_wealthfolio.py index 56f9a52..d8969f8 100644 --- a/tests/sinks/test_wealthfolio.py +++ b/tests/sinks/test_wealthfolio.py @@ -44,10 +44,11 @@ def _client(handler: httpx.MockTransport, session_path: Path) -> WealthfolioSink def _login_ok(req: httpx.Request) -> httpx.Response: assert req.url.path == "/api/v1/auth/login" body = json.loads(req.content) - assert body == {"username": "viktor", "password": "hunter2"} + # Wealthfolio 3.2 LoginRequest is password-only. + assert body == {"password": "hunter2"} return httpx.Response( 200, - json={"ok": True}, + json={"authenticated": True, "expiresIn": 604800}, headers={"set-cookie": "wf_token=abc123; Path=/api; HttpOnly"}, )