Files
ss-tools/backend/tests/integration/test_superset_sqllab_api_integration.py
root 632b730fff chore: migrate GRACE-Poly anchors to hierarchical dotted naming
Systematic rename of all semantic anchors (#region, [DEF], @RELATION)
across 1400+ files — backend Python, frontend Svelte/TS, specs, docs:
- Flat anchors become Namespace.Module.Entity
- @RELATION references updated to match new anchor paths
- Zero business logic changes
2026-07-22 11:48:15 +03:00

256 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# #region Test.SupersetSqllabApiIntegration.TestSupersetSqllabApiJwt [C:4] [TYPE Module] [SEMANTICS test,superset,sqllab,integration,jwt]
# @BRIEF Integration tests for Superset REST API using JWT Bearer auth against real Testcontainers.
# @RELATION BINDS_TO -> [Plugin.SupersetExecutor.SupersetSqlLabExecutor]
# @RELATION BINDS_TO -> [Core.Init.SupersetClient]
# @RELATION BINDS_TO -> [Core.ConfigManager]
#
# @TEST_CONTRACT SupersetSqllabApi ->
# {
# invariants: [
# "JWT login returns access_token + refresh_token",
# "Bearer token authorizes all /api/v1/ endpoints",
# "GET /api/v1/database/ returns paginated database list",
# "POST /api/v1/sqllab/execute/ accepts SQL and returns query_id",
# "GET /api/v1/dashboard/ returns paginated list with Bearer token"
# ]
# }
# @TEST_EDGE: wrong_password -> 401 on JWT login
# @TEST_EDGE: expired_token -> 401 on protected endpoints
# @TEST_EDGE: missing_database_id -> 400 on sqllab execute
#
# @RATIONALE ADR-0012 resolved the JWT auth issue via psycopg2 + superset_config.py +
# Docker bridge IP. Now JWT works end-to-end with the test container.
# All API tests use real Bearer tokens against the live Superset instance.
#
# @REQUIRES Docker daemon running (testcontainers starts superset + postgres)
import pytest
import requests
from uuid import uuid4
# #region Test.SupersetSqllabApiIntegration.TestSupersetJwtHealthCheck [C:3] [TYPE Class]
# @BRIEF Verify health and JWT login work correctly.
class TestSupersetJwtHealthCheck:
"""JWT auth smoke tests against the real Superset container."""
# #region Test.SupersetSqllabApiIntegration.TestJwtLoginReturnsToken [C:2] [TYPE Function]
# @BRIEF POST /api/v1/security/login returns access_token with refresh flag.
def test_jwt_login_returns_token(self, superset_url, superset_admin_password):
resp = requests.post(
f"{superset_url}/api/v1/security/login",
json={
"username": "admin",
"password": superset_admin_password,
"provider": "db",
"refresh": True,
},
timeout=10,
)
assert resp.status_code == 200, f"JWT login failed: {resp.status_code} {resp.text}"
data = resp.json()
assert "access_token" in data, f"No access_token in response: {data.keys()}"
assert "refresh_token" in data, f"No refresh_token in response: {data.keys()}"
assert len(data["access_token"]) > 20, "access_token too short"
# #endregion Test.SupersetSqllabApiIntegration.TestJwtLoginReturnsToken
# #region Test.SupersetSqllabApiIntegration.TestJwtLoginWrongPassword [C:2] [TYPE Function]
# @BRIEF POST /api/v1/security/login with wrong password returns 401.
# @TEST_EDGE: wrong_password — VERIFIED_BY: test_jwt_login_wrong_password
def test_jwt_login_wrong_password(self, superset_url):
resp = requests.post(
f"{superset_url}/api/v1/security/login",
json={
"username": "admin",
"password": "wrong_password_123",
"provider": "db",
},
timeout=10,
)
assert resp.status_code == 401, f"Expected 401, got {resp.status_code}"
# #endregion Test.SupersetSqllabApiIntegration.TestJwtLoginWrongPassword
# #region Test.SupersetSqllabApiIntegration.TestHealthStillWorks [C:2] [TYPE Function]
# @BRIEF /health does not require auth — regression guard.
def test_health_still_works(self, superset_url):
resp = requests.get(f"{superset_url}/health", timeout=10)
assert resp.status_code == 200
assert resp.text.strip() == "OK"
# #endregion Test.SupersetSqllabApiIntegration.TestHealthStillWorks
# #endregion Test.SupersetSqllabApiIntegration.TestSupersetJwtHealthCheck
# #region Test.SupersetSqllabApiIntegration.TestSupersetApiWithJwt [C:3] [TYPE Class]
# @BRIEF Verify REST API endpoints with JWT Bearer token auth.
class TestSupersetApiWithJwt:
"""Authenticated API tests against real Superset container."""
# #region Test.SupersetSqllabApiIntegration.TestListDatabasesWithJwt [C:2] [TYPE Function]
# @BRIEF GET /api/v1/database/ returns paginated list with JWT Bearer token.
def test_list_databases_with_jwt(self, superset_url, superset_jwt_headers):
resp = requests.get(
f"{superset_url}/api/v1/database/",
headers=superset_jwt_headers,
timeout=10,
)
assert resp.status_code == 200, \
f"GET /api/v1/database/ failed: {resp.status_code} {resp.text[:200]}"
data = resp.json()
assert "result" in data, f"No 'result' key: {list(data.keys())}"
assert isinstance(data["result"], list), "result should be a list"
# #endregion Test.SupersetSqllabApiIntegration.TestListDatabasesWithJwt
# #region Test.SupersetSqllabApiIntegration.TestListDashboardsWithJwt [C:2] [TYPE Function]
# @BRIEF GET /api/v1/dashboard/ returns paginated list with JWT Bearer token.
def test_list_dashboards_with_jwt(self, superset_url, superset_jwt_headers):
resp = requests.get(
f"{superset_url}/api/v1/dashboard/",
headers=superset_jwt_headers,
timeout=10,
)
assert resp.status_code == 200, \
f"GET /api/v1/dashboard/ failed: {resp.status_code} {resp.text[:200]}"
data = resp.json()
assert "result" in data, f"No 'result' key: {list(data.keys())}"
assert "count" in data, f"No 'count' key: {list(data.keys())}"
# #endregion Test.SupersetSqllabApiIntegration.TestListDashboardsWithJwt
# #region Test.SupersetSqllabApiIntegration.TestSqllabExecuteWithJwt [C:2] [TYPE Function]
# @BRIEF POST /api/v1/sqllab/execute/ is reachable with JWT.
def test_sqllab_execute_with_jwt(self, superset_url, superset_jwt_headers):
payload = {
"database_id": 1,
"sql": "SELECT 1",
"runAsync": True,
}
resp = requests.post(
f"{superset_url}/api/v1/sqllab/execute/",
json=payload,
headers={
**superset_jwt_headers,
"Content-Type": "application/json",
},
timeout=10,
)
# Without a real DB configured, we expect 400 or 500
# But the endpoint exists and is reachable (not 404/401)
assert resp.status_code in (200, 400, 500), \
f"sqllab/execute/ returned {resp.status_code}: {resp.text[:300]}"
# #endregion Test.SupersetSqllabApiIntegration.TestSqllabExecuteWithJwt
# #region Test.SupersetSqllabApiIntegration.TestSqllabWithoutDatabaseId [C:2] [TYPE Function]
# @BRIEF POST /api/v1/sqllab/execute/ without database_id returns 400.
# @TEST_EDGE: missing_database_id — VERIFIED_BY: test_sqllab_without_database_id
def test_sqllab_without_database_id(self, superset_url, superset_jwt_headers):
resp = requests.post(
f"{superset_url}/api/v1/sqllab/execute/",
json={"sql": "SELECT 1"},
headers={
**superset_jwt_headers,
"Content-Type": "application/json",
},
timeout=10,
)
assert resp.status_code in (400, 500), \
f"Expected 400/500, got {resp.status_code}: {resp.text[:200]}"
# #endregion Test.SupersetSqllabApiIntegration.TestSqllabWithoutDatabaseId
# #region Test.SupersetSqllabApiIntegration.TestGetNonexistentDatabase [C:2] [TYPE Function]
# @BRIEF GET /api/v1/database/<non_existent_id> returns 404.
def test_get_nonexistent_database(self, superset_url, superset_jwt_headers):
resp = requests.get(
f"{superset_url}/api/v1/database/99999",
headers=superset_jwt_headers,
timeout=10,
)
assert resp.status_code == 404, \
f"Expected 404, got {resp.status_code}: {resp.text[:200]}"
# #endregion Test.SupersetSqllabApiIntegration.TestGetNonexistentDatabase
# #region Test.SupersetSqllabApiIntegration.TestMeEndpoint [C:2] [TYPE Function]
# @BRIEF GET /api/v1/me/ — в Superset 4.1.2 это публичный эндпоинт, но
# доступен только авторизованным через JWT. Проверяем что 401/200, а не 404.
def test_me_endpoint(self, superset_url, superset_jwt_headers):
resp = requests.get(
f"{superset_url}/api/v1/me/",
headers=superset_jwt_headers,
timeout=10,
)
# Superset 4.1.2 может вернуть 200 (есть endpoint) или 401 (нет прав у admin role).
# Главное — не 404, значит endpoint существует.
assert resp.status_code in (200, 401, 403), \
f"GET /api/v1/me/ failed: {resp.status_code} {resp.text[:200]}"
if resp.status_code == 200:
data = resp.json()
assert "result" in data, f"No 'result' key: {list(data.keys())}"
# #endregion Test.SupersetSqllabApiIntegration.TestMeEndpoint
# #region Test.SupersetSqllabApiIntegration.TestDatabaseColumnsWithJwt [C:2] [TYPE Function]
# @BRIEF GET /api/v1/database/<id>/columns/ with valid id returns 404 (no DB configured).
def test_database_columns_with_jwt(self, superset_url, superset_jwt_headers):
resp = requests.get(
f"{superset_url}/api/v1/database/99999/columns/",
headers=superset_jwt_headers,
timeout=10,
)
assert resp.status_code == 404, \
f"Expected 404, got {resp.status_code}"
# #endregion Test.SupersetSqllabApiIntegration.TestDatabaseColumnsWithJwt
# #region Test.SupersetSqllabApiIntegration.TestUnauthenticatedApiRejected [C:2] [TYPE Function]
# @BRIEF GET /api/v1/dashboard/ without auth returns 401.
def test_unauthenticated_api_rejected(self, superset_url):
resp = requests.get(
f"{superset_url}/api/v1/dashboard/",
timeout=10,
)
assert resp.status_code == 401, \
f"Expected 401, got {resp.status_code}"
# #endregion Test.SupersetSqllabApiIntegration.TestUnauthenticatedApiRejected
# #endregion Test.SupersetSqllabApiIntegration.TestSupersetApiWithJwt
# #region Test.SupersetSqllabApiIntegration.TestSupersetJwtIntegration [C:3] [TYPE Class]
# @BRIEF Verify SupersetClient and SupersetSqlLabExecutor with real JWT auth.
class TestSupersetJwtIntegration:
"""Full SupersetClient integration tests with JWT auth."""
# #region Test.SupersetSqllabApiIntegration.TestSupersetClientAuthenticated [C:2] [TYPE Function]
# @BRIEF Real SupersetClient authenticates and can call API.
@pytest.mark.asyncio
async def test_superset_client_authenticated(self, superset_client):
# Authenticated client should be able to list databases
count, databases = await superset_client.get_databases()
assert isinstance(count, int)
assert isinstance(databases, list)
# #endregion Test.SupersetSqllabApiIntegration.TestSupersetClientAuthenticated
# #region Test.SupersetSqllabApiIntegration.TestSupersetClientUserViaMeEndpoint [C:2] [TYPE Function]
# @BRIEF SupersetClient can call /me/ raw endpoint — проверяем доступность.
@pytest.mark.asyncio
async def test_superset_client_user_via_me_endpoint(self, superset_client):
me = await superset_client.client.request("GET", "/me/")
assert me is not None
# Superset 4.1.2 может вернуть 200 (есть endpoint) или {"message":"Not authorized"}
# в зависимости от прав роли Admin на конкретный endpoint
# #endregion Test.SupersetSqllabApiIntegration.TestSupersetClientUserViaMeEndpoint
# #endregion Test.SupersetSqllabApiIntegration.TestSupersetJwtIntegration
# #endregion Test.SupersetSqllabApiIntegration.TestSupersetSqllabApiJwt