Префикс /api/v1, operationId = "<tag>_<name>", чтобы Dart-клиент получил методы вроде accountsList, а не list_accounts_api_v1_accounts_get. Ошибки — application/problem+json. Auth: access-JWT на час + refresh на 30 дней, который хранится хэшем и ротируется, логин ограничен по частоте в памяти. Деньги в JSON — всегда позиционные строки (api/schemas/common.py): float в проводе потерял бы копейки, которые NUMERIC(24,10) бережёт. Тестовый harness поднимает свой Postgres через pg_ctl (pytest-postgresql), мигрирует его один раз на сессию и усекает таблицы после каждого теста.
32 lines
1.3 KiB
Python
32 lines
1.3 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
|
|
@pytest.fixture
|
|
async def web_client(migrated: str, tmp_path: Path, monkeypatch):
|
|
(tmp_path / "index.html").write_text("<!doctype html><title>spa</title>")
|
|
(tmp_path / "main.dart.js").write_text("console.log(1)")
|
|
monkeypatch.setenv("WEB_DIR", str(tmp_path))
|
|
from fintracker.api.app import create_app
|
|
from fintracker.config import get_settings
|
|
from fintracker.db import reset_engine
|
|
|
|
get_settings.cache_clear()
|
|
app = create_app()
|
|
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
|
|
yield c
|
|
get_settings.cache_clear()
|
|
await reset_engine()
|
|
|
|
|
|
async def test_spa_fallback_and_api_precedence(web_client):
|
|
assert (await web_client.get("/")).text.startswith("<!doctype html>")
|
|
assert (await web_client.get("/login")).text.startswith("<!doctype html>") # deep link
|
|
assert (await web_client.get("/main.dart.js")).text == "console.log(1)"
|
|
assert (await web_client.get("/api/v1/health")).json()["status"] == "ok"
|
|
r = await web_client.get("/api/v1/nope")
|
|
assert r.status_code == 404 and r.headers["content-type"].startswith("application/problem+json")
|
|
assert (await web_client.get("/../etc/passwd")).text.startswith("<!doctype html>")
|