fix(superset): resolve JWT auth — install psycopg2 + superset_config.py

Root cause: Superset 4.1.2 ignores SQLALCHEMY_DATABASE_URI env var —
always falls back to SQLite. With separate init+web containers, the
init container's SQLite DB is lost → web container has no admin user
→ JWT login returns 401.

Fix:
- Install psycopg2-binary at container start (python -m pip install)
- Create /tmp/superset_config.py via heredoc that reads SUPERSET_DB_URI
- Set SUPERSET_CONFIG_PATH=/tmp/superset_config.py
- Use Docker bridge IP (not localhost) for Postgres from inside container

Now all 10 integration tests pass, including:
- test_jwt_login_success (access_token + refresh_token)
- test_jwt_authenticated_api_call (Bearer token → /api/v1/dashboard/)
- 4 health checks + 2 form-auth + 2 client construct
This commit is contained in:
2026-06-11 15:23:12 +03:00
parent 1c9c2c298e
commit d24745a01e
3 changed files with 117 additions and 30 deletions

View File

@@ -6,11 +6,10 @@
# @RATIONALE Проект ss-tools интенсивно взаимодействует с Superset REST API (SupersetClient, SupersetSqlLabExecutor, translate-плагины). До принятия этого решения все тесты Superset-клиента использовали моки (unittest.mock.AsyncMock), что не покрывало реальные сценарии: таймауты, редиректы, изменения API между версиями Superset. Моки также не ловят регрессии в обработке ответов (изменение структуры JSON, новые поля в пагинации).
# @RATIONALE Testcontainers выбран вместо docker-compose для тестов потому что: (a) гарантирует изоляцию между тестовыми сессиями — каждый запуск получает свежий контейнер на случайном порту, (b) не требует предварительного запуска инфраструктуры, (c) автоматически чистит контейнеры после завершения сессии, (d) единообразен с существующими PostgresContainer/ClickHouseContainer — команда уже знает этот паттерн.
# @RATIONALE Двухконтейнерная архитектура (init + web) выбрана потому что Superset требует раздельной инициализации: `superset db upgrade` создаёт таблицы в БД, `superset fab create-admin` создаёт пользователя, `superset init` инициализирует роли и permission'ы. Только после этого можно запускать веб-сервер. Попытка объединить всё в один контейнер через exec() невозможна — нельзя сменить entrypoint контейнера с init-режима на web-сервер.
# @RATIONALE КРИТИЧЕСКОЕ ОТКРЫТИЕ (2026-06-11): образ Superset 4.1.2 **не содержит psycopg2** — при указании `SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://...` Superset **молча фоллбечится на SQLite** (`/app/superset_home/superset.db`). При двухконтейнерной архитектуре init-контейнер создаёт таблицы и пользователя в своём SQLite, затем умирает. Web-контейнер стартует с новым пустым SQLite — пользователь admin не существует → `auth_user_db` возвращает None → JWT login: 401 "Not authorized". **Решение:** `python -m pip install psycopg2-binary -q` в init и web контейнерах. После установки драйвера Superset подключается к общему Postgres (`superset_meta`), пользователь виден обоим контейнерам, JWT-авторизация работает.
# @REJECTED Docker Compose для тестов — отвергнут, так как требует предварительного `docker compose up`, порты фиксированы и могут конфликтовать, изоляция между сессиями слабее.
# @REJECTED Superset 6.1.0 (latest) — отвергнут, потому что: (1) образ не содержит драйвер PostgreSQL (psycopg2), требуется `pip install` при старте, что добавляет ~30s к запуску, (2) конфигурация через `SUPERSET__` префикс энв-переменных вместо `SQLALCHEMY_DATABASE_URI`, (3) форма логина падает с ошибкой `no such table: themes` при использовании SQLite, что делает контейнер непригодным даже для smoke-тестов без Postgres. Superset версии 5.x не существует — нумерация перескочила с 4.x на 6.x.
# @REJECTED Superset 4.1.2 с psycopg2-binary — отвергнут, так как pip install add-hock ломает идемпотентность контейнера и добавляет хрупкую зависимость от network на старте.
# @REJECTED Использование latest тега — отвергнуто, так как версионный дрифт ломает тесты без предупреждения. Версия 4.1.2 зафиксирована.
# @REJECTED Полноценная JWT-авторизация в тестовом контейнере — отвергнута на данном этапе, потому что `/api/v1/security/login` возвращает 401 для пользователя, созданного через `superset fab create-admin`. Это ограничение Flask-AppBuilder: команда `fab create-admin` даёт роль Admin, но JWT REST API требует дополнительных permission'ов (`can_this_form_post on SecurityRestApi`), которые не назначаются автоматически. Form-based логин (POST /login/) работает корректно. Для тестов SupersetClient (который использует JWT) нужен пре-конфигурированный инстанс Superset.
# @REJECTED Superset 6.1.0 (latest) — отвергнут, потому что: (1) образ не содержит драйвер PostgreSQL (`psycopg2`, `pg8000`, `asyncpg` — ни одного), (2) требуется `SUPERSET__SQLALCHEMY_DATABASE_URI` вместо `SQLALCHEMY_DATABASE_URI`, (3) `pip install` в 6.x ставит в системный Python, а Superset использует venv (`/app/.venv/bin/python`) — нужно указывать `/app/.venv/bin/pip`, (4) форма логина падает с `no such table: themes` при использовании SQLite. Суммарно 6.x добавляет 30+ секунд к старту и требует нестандартных workaround'ов.
# @REJECTED Superset версии 5.x — не существует. Нумерация перескочила с 4.x на 6.x.
## Decision
@@ -62,16 +61,15 @@ backend/tests/integration/conftest.py
|------|-------|--------|
| Health | `/health`, `/login/`, `/api/v1/database/` | ✅ 4/4 |
| Auth (form) | Логин → session cookie, `/api/v1/me/` | ✅ 2/2 |
| Auth (JWT) | `/api/v1/security/login` → access_token + refresh_token | ✅ 2/2 |
| API (JWT) | `/api/v1/dashboard/` с Bearer token | ✅ 1/1 |
| SupersetClient | Конструкция клиента, api_base_url | ✅ 2/2 |
| Auth (JWT) | `/api/v1/security/login` | ❌ 401 — требуется FAB-конфигурация |
### Ограничения
1. **JWT-авторизация не работает из коробки.** `POST /api/v1/security/login` возвращает 401 "Not authorized" для пользователя Admin, созданного через `fab create-admin`. Это ограничение Flask-AppBuilder: роль Admin не включает permission `can_this_form_post on SecurityRestApi`. Для полноценных тестов SupersetClient требуется пре-конфигурированный инстанс Superset с назначенными правами.
1. **Требуется `pip install psycopg2-binary` при старте контейнера.** Образ Superset 4.1.2 не содержит драйвер PostgreSQL. Без него Superset молча фоллбечится на SQLite, что ломает двухконтейнерную архитектуру (init-контейнер теряет данные). `pip install` добавляет ~5s к холодному старту. Первый запуск также требует загрузки пакета из PyPI.
2. **Метаданные Superset хранятся в SQLite внутри контейнера.** Хотя `SQLALCHEMY_DATABASE_URI` указывает на Postgres, образ 4.1.2 не содержит драйвер psycopg2. Superset молча фоллбечится на SQLite. Для smoke-тестов это приемлемо — `/health`, `/login/`, базовые API работают. Для тестов, требующих персистентности между перезапусками, нужен кастомный Dockerfile с `psycopg2-binary`.
3. **Версия 6.1.0 несовместима без доработок.** Образ не содержит драйвер PostgreSQL, не принимает `SQLALCHEMY_DATABASE_URI` без префикса `SUPERSET__`, и форма логина падает при использовании SQLite (требует таблицу themes).
2. **Версия 6.1.0 несовместима без доработок.** Образ не содержит драйвер PostgreSQL, не принимает `SQLALCHEMY_DATABASE_URI` без префикса `SUPERSET__`, и форма логина падает при использовании SQLite (требует таблицу themes). `pip install` требует указания `/app/.venv/bin/pip`.
## Enforcement