from __future__ import annotations from datetime import UTC, datetime, timedelta from decimal import Decimal from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.pool import StaticPool from app.core.config import get_settings from app.db.base import Base from app.models.agent_run import AgentRun from app.models.financial_record import ExpenseClaim from app.models.risk_observation import RiskObservation from app.services.digital_employee_finance_report_task import ( FINANCE_REPORT_TASK_TYPE, DigitalEmployeeFinanceReportTaskService, ) def build_session() -> Session: engine = create_engine( "sqlite+pysqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) Base.metadata.create_all(bind=engine) session_factory = sessionmaker(bind=engine, autoflush=False, autocommit=False) return session_factory() def test_finance_report_task_generates_pdf_and_agent_record(monkeypatch, tmp_path) -> None: monkeypatch.setenv("STORAGE_ROOT_DIR", str(tmp_path)) get_settings.cache_clear() now = datetime.now(UTC) with build_session() as db: db.add( ExpenseClaim( claim_no="RE-REPORT-001", employee_name="林嘉宁", department_name="市场部", expense_type="travel", reason="客户拜访", location="上海", amount=Decimal("3600.00"), invoice_count=2, occurred_at=now - timedelta(days=2), submitted_at=now - timedelta(days=2), status="paid", approval_stage="已付款", risk_flags_json=[], hermes_risk_flag=False, created_at=now - timedelta(days=2), updated_at=now - timedelta(days=1), ) ) db.add( RiskObservation( observation_key="risk-report-001", subject_type="expense_claim", subject_key="RE-REPORT-001", subject_label="RE-REPORT-001", claim_no="RE-REPORT-001", risk_type="policy", risk_signal="amount_outlier", title="金额异常", risk_level="high", status="pending_review", created_at=now - timedelta(days=1), updated_at=now - timedelta(days=1), ) ) db.commit() result = DigitalEmployeeFinanceReportTaskService(db).generate_report( report_type="weekly", send_email=True, dry_run_email=True, ) pdf_path = tmp_path / result["pdf"]["storage_key"] html_path = pdf_path.with_name("report.html") runs = [ run for run in db.query(AgentRun).filter(AgentRun.agent == "hermes").all() if (run.route_json or {}).get("task_type") == FINANCE_REPORT_TASK_TYPE ] assert pdf_path.exists() assert pdf_path.read_bytes().startswith(b"%PDF") assert html_path.exists() assert result["delivery"]["status"] in {"dry_run", "pending_configuration"} assert result["summary"]["reimbursement_count"] >= 1 assert runs assert runs[0].status == "succeeded" assert runs[0].route_json["report_delivery"]["pdf"]["storage_key"].endswith("report.pdf") get_settings.cache_clear()