mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 10:08:46 +02:00
140 lines
4.9 KiB
Python
140 lines
4.9 KiB
Python
"""Chat-run audit persistence and redacted HTTP access logs."""
|
|
|
|
import asyncio
|
|
from datetime import timedelta
|
|
|
|
from agent_framework_ag_ui import AGUIThreadSnapshot
|
|
from starlette.requests import Request
|
|
from starlette.responses import Response
|
|
from sqlmodel import select
|
|
|
|
import app.agent.chat_audit as chat_audit
|
|
import app.agent.snapshot_store as snapshot_store_module
|
|
from app.agent.snapshot_store import PostgresSnapshotStore
|
|
from app.models.models import ChatRunLog
|
|
from app.timeutil import utcnow
|
|
|
|
|
|
def _audit_store(monkeypatch, engine):
|
|
monkeypatch.setattr(chat_audit, "engine", engine)
|
|
monkeypatch.setattr(snapshot_store_module, "engine", engine)
|
|
return PostgresSnapshotStore()
|
|
|
|
|
|
def test_snapshot_completes_chat_run_with_tools_and_response(monkeypatch, engine, session):
|
|
store = _audit_store(monkeypatch, engine)
|
|
run_id = chat_audit.create_chat_run(
|
|
request_id="req-1",
|
|
agui_run_id="run-1",
|
|
thread_id="main",
|
|
messages=[{"role": "user", "content": "¿Cuál es mi saldo?"}],
|
|
client_ip="203.0.113.7",
|
|
user_agent="Mozilla/5.0 TestBrowser/1.0",
|
|
browser="TestBrowser",
|
|
)
|
|
token = chat_audit.set_current_chat_run(run_id)
|
|
try:
|
|
asyncio.run(
|
|
store.save(
|
|
scope="default",
|
|
thread_id="resp_123",
|
|
snapshot=AGUIThreadSnapshot(
|
|
messages=[
|
|
{"id": "u1", "role": "user", "content": "¿Cuál es mi saldo?"},
|
|
{
|
|
"id": "a1",
|
|
"role": "assistant",
|
|
"content": None,
|
|
"tool_calls": [
|
|
{
|
|
"id": "call-1",
|
|
"type": "function",
|
|
"function": {"name": "get_net_worth", "arguments": "{}"},
|
|
}
|
|
],
|
|
},
|
|
{"id": "t1", "role": "tool", "tool_call_id": "call-1", "content": "{\"net_crc\": 1}"},
|
|
{"id": "a2", "role": "assistant", "content": "Tu saldo es ₡1."},
|
|
]
|
|
),
|
|
)
|
|
)
|
|
finally:
|
|
chat_audit.reset_current_chat_run(token)
|
|
|
|
row = session.get(ChatRunLog, run_id)
|
|
assert row is not None
|
|
assert row.status == "completed"
|
|
assert row.agui_run_id == "run-1"
|
|
assert row.response_thread_id == "resp_123"
|
|
assert row.user_prompt == "¿Cuál es mi saldo?"
|
|
assert row.client_ip == "203.0.113.7"
|
|
assert row.user_agent == "Mozilla/5.0 TestBrowser/1.0"
|
|
assert row.browser == "TestBrowser"
|
|
assert row.assistant_response == "Tu saldo es ₡1."
|
|
assert row.tool_calls == [
|
|
{
|
|
"id": "call-1",
|
|
"name": "get_net_worth",
|
|
"arguments": "{}",
|
|
"result": '{"net_crc": 1}',
|
|
}
|
|
]
|
|
|
|
|
|
def test_failure_and_retention_cleanup(monkeypatch, engine, session):
|
|
_audit_store(monkeypatch, engine)
|
|
failed_id = chat_audit.create_chat_run(
|
|
request_id="req-failed", agui_run_id="run-failed", thread_id="main", messages=[]
|
|
)
|
|
chat_audit.mark_chat_run_failed(failed_id, "RuntimeError: provider unavailable")
|
|
assert session.get(ChatRunLog, failed_id).status == "failed"
|
|
|
|
expired = ChatRunLog(
|
|
request_id="req-expired",
|
|
model="gpt-5.6-luna",
|
|
expires_at=utcnow() - timedelta(seconds=1),
|
|
)
|
|
session.add(expired)
|
|
session.commit()
|
|
|
|
assert chat_audit.purge_expired_chat_runs() == 1
|
|
assert session.exec(select(ChatRunLog).where(ChatRunLog.request_id == "req-expired")).first() is None
|
|
|
|
|
|
def test_api_access_log_never_contains_request_body(monkeypatch):
|
|
from app.main import log_api_request
|
|
|
|
logged = []
|
|
monkeypatch.setattr("app.main.log_event", lambda event, **fields: logged.append((event, fields)))
|
|
scope = {
|
|
"type": "http",
|
|
"method": "POST",
|
|
"path": "/api/auth/login",
|
|
"raw_path": b"/api/auth/login",
|
|
"query_string": b"",
|
|
"headers": [
|
|
(b"x-real-ip", b"203.0.113.9"),
|
|
(b"user-agent", b"Mozilla/5.0 Chrome/138.0.0.0 Safari/537.36"),
|
|
],
|
|
"scheme": "http",
|
|
"server": ("testserver", 80),
|
|
"client": ("testclient", 1234),
|
|
}
|
|
request = Request(scope)
|
|
|
|
async def call_next(_request):
|
|
return Response(status_code=201)
|
|
|
|
response = asyncio.run(log_api_request(request, call_next))
|
|
assert response.headers["X-Request-ID"]
|
|
assert logged[0][0] == "api_request"
|
|
fields = logged[0][1]
|
|
assert fields["path"] == "/api/auth/login"
|
|
assert fields["status_code"] == 201
|
|
assert fields["client_ip"] == "203.0.113.9"
|
|
assert fields["browser"] == "Chrome"
|
|
assert fields["user_agent"] == "Mozilla/5.0 Chrome/138.0.0.0 Safari/537.36"
|
|
assert "password" not in fields
|
|
assert "body" not in fields
|