Files
group_xinghuo_jinrong/tests/test_sprint0_infrastructure.py
T
zhanghongyu_0626 f856ab4aa6 fix(tests): Resolve pytest session binding issues and update test cases
- Updated test files to import `AgentSessionLocal` from `advisor_db` instead of directly, preventing session binding to the real database during tests.
- Fixed 6 test cases to use the new login token utility, ensuring consistency across authentication methods.
- Adjusted customer risk codes in `test_convert_confirm.py` to reflect changes in customer classification (C3 to C4).
- Verified that changes resulted in zero database pollution during test runs, maintaining integrity of the testing environment.
- Documented findings and updates in the relevant test logs and memory files, ensuring clarity on the current state of tests and defects.
2026-09-13 23:21:34 +08:00

209 lines
6.9 KiB
Python

from pathlib import Path
from uuid import uuid4
import pytest
from app import advisor_db
from app.advisor_exceptions import OwnershipDeniedError
from app.api.advisor_auth_adapter import AdvisorAuthContext
from app.model.entities import AuditLog
from app.service.audit_service import AuditService, InMemoryAuditRepository
from app.service.ownership_service import OwnershipService
ROOT = Path(__file__).resolve().parents[1]
def test_requirements_include_sprint0_approved_dependencies():
requirements = (ROOT / "requirements.txt").read_text(encoding="utf-8")
for package_name in [
"pytest",
"httpx",
]:
assert package_name in requirements
def test_advisor_agent_migration_sql_exists():
path = ROOT / "scripts" / "agent" / "migrate-advisor-agent-sprint1-3.sql"
assert path.exists()
text = path.read_text(encoding="utf-8")
assert "compliance_rule" in text
assert "kyc_session" in text
def test_core_reset_script_runs_core_seed_and_optional_neo4j_sync():
reset_script = (ROOT / "scripts" / "core" / "reset.ps1").read_text(encoding="utf-8")
assert "scripts/core/01-ddl.sql" in reset_script
assert "python scripts/sync/sync_advisor_rel.py" in reset_script
assert "[switch]$SkipNeo4j" in reset_script
assert "if (-not $SkipNeo4j)" in reset_script
assert "python scripts/sync/sync_neo4j.py" in reset_script
def test_core_reset_script_uses_run_sql_file_helper():
reset_script = (ROOT / "scripts" / "core" / "reset.ps1").read_text(encoding="utf-8")
assert "scripts/dev/run_sql_file.py" in reset_script
assert "fix_utf8_seed.py" in reset_script
def test_sprint0_test_suite_covers_required_quality_gates():
tests_text = "\n".join(
path.read_text(encoding="utf-8")
for path in sorted((ROOT / "tests").glob("test_sprint0_*.py"))
)
required_checks = [
"test_requirements_include_sprint0_approved_dependencies",
"test_advisor_agent_migration_sql_exists",
"test_core_reset_script_runs_core_seed_and_optional_neo4j_sync",
"test_sqlalchemy_audit_repository_persists_append_only_event",
"test_trace_id_header_is_reused_in_response_and_rbac_audit_event",
"test_missing_trace_id_generates_response_header_on_unauthenticated_request",
"test_platform_compliance_suitability_unchanged",
]
for check_name in required_checks:
assert check_name in tests_text
class FakeCoreRepo:
def __init__(self, assigned: bool) -> None:
self.assigned = assigned
def is_advisor_assigned(self, advisor_id: str, customer_id: str) -> bool:
return self.assigned and advisor_id == "ADV-TEST-001" and customer_id == "CUST-001"
def test_ownership_guard_allows_assigned_advisor_customer():
service = OwnershipService(FakeCoreRepo(assigned=True))
auth = AdvisorAuthContext(
user_id="STAFF-10086",
token_type="staff",
roles=["advisor"],
permissions=["advisor:workspace"],
advisor_id="ADV-TEST-001",
trace_id="trace-owner-ok",
)
service.assert_customer_access(auth, "CUST-001")
def test_ownership_guard_denies_unassigned_advisor_customer():
service = OwnershipService(FakeCoreRepo(assigned=False))
auth = AdvisorAuthContext(
user_id="STAFF-10086",
token_type="staff",
roles=["advisor"],
permissions=["advisor:workspace"],
advisor_id="ADV-TEST-001",
trace_id="trace-owner-deny",
)
with pytest.raises(OwnershipDeniedError):
service.assert_customer_access(auth, "CUST-999")
def test_audit_service_appends_events_without_mutating_prior_records():
repository = InMemoryAuditRepository()
service = AuditService(repository)
service.record(
trace_id="trace-audit-001",
event_type="auth_login",
actor_id="advisor_test",
decision="success",
)
service.record(
trace_id="trace-audit-002",
event_type="rbac_denied",
actor_id="advisor_test",
decision="audit:read",
)
events = service.list_events()
assert len(events) == 2
assert events[0].trace_id == "trace-audit-001"
assert events[1].trace_id == "trace-audit-002"
def test_sqlalchemy_audit_repository_persists_append_only_event():
from app.service import audit_service as audit_module
trace_id = f"trace-db-{uuid4().hex}"
repository_class = getattr(audit_module, "SqlAlchemyAuditRepository", None)
assert repository_class is not None
repository = repository_class(advisor_db.AgentSessionLocal)
service = AuditService(repository)
service.record(
trace_id=trace_id,
event_type="audit_repository_test",
actor_id="test_runner",
decision="persisted",
input_summary={"source": "pytest"},
)
with advisor_db.AgentSessionLocal() as session:
event = session.query(AuditLog).filter(AuditLog.trace_id == trace_id).one()
assert event.event_type == "audit_repository_test"
assert event.actor_id == "test_runner"
assert event.decision == "persisted"
assert event.input_summary == {"source": "pytest"}
def test_unknown_staff_login_is_rejected():
from fastapi.testclient import TestClient
from app.main import app
response = TestClient(app).post(
"/api/auth/login",
json={"actor_id": "STAFF-UNKNOWN-ACTOR", "token_type": "staff"},
)
assert response.status_code == 401
def test_trace_id_header_is_reused_in_response_and_rbac_audit_event():
from fastapi.testclient import TestClient
from app.main import app
trace_id = f"trace-rbac-db-{uuid4().hex}"
login = TestClient(app).post(
"/api/auth/login",
json={"actor_id": "STAFF-10086", "token_type": "staff"},
)
token = login.json()["data"]["access_token"]
response = TestClient(app).post(
"/api/advisor-agent/compliance/rules",
json={
"rule_type": "keyword",
"pattern": "trace-id-rbac-test",
"severity": "block",
"category": "return_promise",
"suggestion": "no promise",
},
headers={"Authorization": f"Bearer {token}", "X-Trace-Id": trace_id},
)
assert response.status_code == 403
assert response.headers["X-Trace-Id"] == trace_id
assert response.json()["trace_id"] == trace_id
with advisor_db.AgentSessionLocal() as session:
event = (
session.query(AuditLog)
.filter(AuditLog.trace_id == trace_id, AuditLog.event_type == "rbac_denied")
.one()
)
assert event.actor_id == "STAFF-10086"
assert event.decision == "compliance:rule:write"
def test_missing_trace_id_generates_response_header_on_unauthenticated_request():
from fastapi.testclient import TestClient
from app.main import app
response = TestClient(app).get("/api/advisor-agent/compliance/ping")
assert response.status_code == 401
generated_trace_id = response.headers["X-Trace-Id"]
assert generated_trace_id
assert response.json()["trace_id"] == generated_trace_id