Backend: - Add validate_mapping_database_ownership() to verify source/target UUIDs belong to declared environments before persisting mappings (mappings.py) - Add API-key environment scoping to get_mappings (filter) and suggest_mappings_api (enforce) (mappings.py) - Add user_id Column to TaskRecord model + Alembic migration (task.py) - Persist task.user_id on save, restore on load (persistence.py) - Wire current_user.id into migrate_dashboards + backup_dashboards task creation (_action_routes.py) - Fix test_migration_routes.py: module-level patch leak → autouse fixture, SupersetClient→AsyncSupersetClient, AsyncMock for sync_environment/run - Fix 7 Pydantic serializer warnings: 'PENDING'→TaskStatus.PENDING in test_tasks.py + import TaskStatus Frontend: - Deepen isDryRunResult(): validate selection field, risk.items entries (all 5 fields), and diff object uuids individually (ExecutorModel.svelte.ts) Prior work included: task password redaction, resume ownership checks, canonical dry-run DTO alignment, migration UI callback fixes, credential exposure reduction, assistant dry-run await fix.
86 lines
4.4 KiB
Python
86 lines
4.4 KiB
Python
# #region Test.Infrastructure.GradioProxy [C:3] [TYPE Module] [SEMANTICS test,nginx,docker,agent,gradio]
|
|
# @BRIEF Verify Gradio agent proxy contracts across nginx, compose, and agent launch config.
|
|
# @RELATION BINDS_TO -> [docker.nginx.conf]
|
|
# @RELATION BINDS_TO -> [docker.nginx.ssl.conf]
|
|
# @RELATION BINDS_TO -> [AgentChat.Config]
|
|
# @TEST_EDGE: docker_dns_name -> Proxy targets compose service name `agent`.
|
|
# @TEST_EDGE: prefix_forwarding -> Proxy preserves `/api/agent/gradio` for Gradio root_path.
|
|
# @TEST_EDGE: auth_forwarding -> Browser Authorization header is forwarded.
|
|
# @TEST_INVARIANT: trace_forwarding -> Valid browser trace IDs cross nginx into backend and agent.
|
|
# @TEST_INVARIANT: proxy_access_events -> nginx stdout contains one JSON access event per request.
|
|
from pathlib import Path
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def _read(relative_path: str) -> str:
|
|
return (PROJECT_ROOT / relative_path).read_text(encoding="utf-8")
|
|
|
|
|
|
def test_nginx_http_gradio_proxy_targets_agent_service_and_preserves_prefix():
|
|
"""HTTP nginx routes Gradio traffic to compose service `agent` without stripping root_path."""
|
|
text = _read("docker/nginx.conf")
|
|
assert "location /api/agent/gradio/" in text
|
|
assert "set $agent_api http://agent:7860;" in text
|
|
assert "superset-tools-agent" not in text
|
|
assert "rewrite ^/api/agent/gradio" not in text
|
|
assert "proxy_set_header Authorization $http_authorization;" in text
|
|
assert "proxy_set_header X-Trace-ID $http_x_trace_id;" in text
|
|
assert "proxy_set_header Connection $connection_upgrade;" in text
|
|
assert "proxy_redirect http://agent:7860/ /api/agent/gradio/;" in text
|
|
|
|
|
|
def test_nginx_ssl_gradio_proxy_matches_http_contract():
|
|
"""SSL nginx keeps the same Gradio proxy invariants as HTTP nginx."""
|
|
text = _read("docker/nginx.ssl.conf")
|
|
assert "map $http_upgrade $connection_upgrade" in text
|
|
assert "location /api/agent/gradio/" in text
|
|
assert "set $agent_api http://agent:7860;" in text
|
|
assert "proxy_set_header X-Trace-ID $http_x_trace_id;" in text
|
|
assert "superset-tools-agent" not in text
|
|
assert "rewrite ^/api/agent/gradio" not in text
|
|
assert "proxy_set_header Authorization $http_authorization;" in text
|
|
assert "proxy_set_header X-Forwarded-Proto https;" in text
|
|
|
|
|
|
def test_nginx_proxy_emits_json_access_logs_and_forwards_trace_ids():
|
|
"""Both nginx modes must make request correlation available on container stdout."""
|
|
for relative_path in ("docker/nginx.conf", "docker/nginx.ssl.conf"):
|
|
text = _read(relative_path)
|
|
assert "log_format observability_json escape=json" in text
|
|
assert "access_log /dev/stdout observability_json;" in text
|
|
assert "error_log /dev/stderr warn;" in text
|
|
assert '"trace_id":"$http_x_trace_id"' in text
|
|
assert '"upstream_response_time":"$upstream_response_time"' in text
|
|
assert text.count("proxy_set_header X-Trace-ID $http_x_trace_id;") == 3
|
|
|
|
|
|
def test_compose_frontend_depends_on_agent_and_agent_uses_root_path():
|
|
"""Compose starts agent before frontend and configures Gradio root_path."""
|
|
for compose_path in ("docker-compose.yml", "docker-compose.enterprise-clean.yml"):
|
|
text = _read(compose_path)
|
|
assert " frontend:" in text
|
|
frontend_block = text.split(" frontend:", 1)[1].split("\n\n", 1)[0]
|
|
assert " - backend" in frontend_block
|
|
assert " - agent" in frontend_block
|
|
agent_block = text.split(" agent:", 1)[1]
|
|
assert "GRADIO_ROOT_PATH: /api/agent/gradio" in agent_block
|
|
|
|
|
|
def test_build_script_generated_enterprise_compose_keeps_agent_proxy_contract():
|
|
"""Release bundle compose heredocs preserve frontend->agent dependency and root_path."""
|
|
text = _read("build.sh")
|
|
assert text.count(" - agent") >= 1
|
|
assert text.count("GRADIO_ROOT_PATH: /api/agent/gradio") >= 1
|
|
|
|
|
|
def test_agent_launch_uses_default_gradio_root_path():
|
|
"""Agent runtime exports `/api/agent/gradio` as Gradio root_path for @gradio/client."""
|
|
config_text = _read("agent/src/ss_tools/agent/_config.py")
|
|
run_text = _read("agent/src/ss_tools/agent/run.py")
|
|
app_text = _read("agent/src/ss_tools/agent/app.py")
|
|
assert 'GRADIO_ROOT_PATH: str = os.getenv("GRADIO_ROOT_PATH", "/api/agent/gradio")' in config_text
|
|
assert "root_path=GRADIO_ROOT_PATH" in run_text
|
|
assert "root_path=GRADIO_ROOT_PATH" in app_text
|
|
# #endregion Test.Infrastructure.GradioProxy
|