from __future__ import annotations import os import sys import types from datetime import datetime, timedelta import pytest import pytest_asyncio from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine os.environ["DATABASE_URL"] = "sqlite+aiosqlite:///:memory:" from app.models.base import Base from app.models.cognitive_conflict import CognitiveConflict from app.models.project import Project from app.models.project_signal_link import ProjectSignalLink from app.models.signal import Signal from app.models.task import Task from app.models.truth_node import TruthNode from app.models.user import User @pytest_asyncio.fixture async def briefing_session(): engine = create_async_engine("sqlite+aiosqlite:///:memory:") async with engine.begin() as conn: await conn.run_sync( lambda sync_conn: Base.metadata.create_all( sync_conn, tables=[ User.__table__, Project.__table__, TruthNode.__table__, Task.__table__, Signal.__table__, ProjectSignalLink.__table__, CognitiveConflict.__table__, ], ) ) maker = async_sessionmaker(engine, expire_on_commit=False) async with maker() as session: yield session await engine.dispose() @pytest.mark.asyncio async def test_generate_personalized_briefing_uses_naive_time_window(briefing_session, monkeypatch) -> None: fixed_now = datetime(2026, 3, 25, 14, 43, 24) stub_database_module = types.ModuleType("app.core.database") stub_database_module.AsyncSessionLocal = None monkeypatch.setitem(sys.modules, "app.core.database", stub_database_module) import app.services.daily_briefing_core.mixin_personal as daily_briefing_personal_module from app.services.daily_briefing_core import DailyBriefingService monkeypatch.setattr(daily_briefing_personal_module, "now_cn_naive", lambda: fixed_now) async def fake_generate_ai_summary(self, user, data, tenant_id): # noqa: ANN001 _ = self, user, data, tenant_id return "今日简报" monkeypatch.setattr(DailyBriefingService, "_generate_ai_summary", fake_generate_ai_summary) user = User( tenant_id=1, username="tester", email="tester@example.com", password_hash="hash", name="测试用户", role="BE", status="active", is_active=True, ) project = Project( tenant_id=1, name="demo-project", title="Demo Project", status="active", ) briefing_session.add_all([user, project]) await briefing_session.flush() truth_node = TruthNode( tenant_id=1, node_type="task", parent_id=None, project_id=project.id, signal_id=None, created_by=user.id, title="修复简报时间窗", content={}, version=1, status="active", ) briefing_session.add(truth_node) await briefing_session.flush() task = Task( tenant_id=1, truth_node_id=truth_node.id, project_id=project.id, assignee_id=user.id, ai_recommended_assignees={}, ai_breakdown_payload=None, manual_overrides=None, title="修复简报时间窗", description="", required_skill_tag_ids=[], status="in_progress", priority="HIGH", estimated_hours=2.0, actual_hours=None, quality_score=None, due_date=None, completed_at=None, created_at=fixed_now - timedelta(hours=4), updated_at=fixed_now - timedelta(hours=2), ) briefing_session.add(task) await briefing_session.commit() briefing = await DailyBriefingService(briefing_session).generate_personalized_briefing( user_id=user.id, tenant_id=1, ) assert briefing["summary"] == "今日简报" assert len(briefing["your_tasks"]) == 1 assert briefing["your_tasks"][0]["title"] == "修复简报时间窗"