# -*- coding: utf-8 -*- """任务监控线程的启动契约。 背景(回归):Flask 单体时代该线程由模块级代码拉起,8446166「移除 Flask」 把实现搬进 services 包时启动点丢失,`_task_monitor_loop` 此后全项目零引用, 监控循环一次都没跑过。这里钉住两条契约: 1. 启动是幂等的(多 worker / 多次构建应用只起一个线程); 2. 应用 lifespan 会启动它(否则心跳超时暂停、超时取消、账本孤儿对账 等全部静默失效)。 """ from fastapi.testclient import TestClient from core import db as core_db from services import tasks as services_tasks def _isolate_data_dir(monkeypatch, tmp_path): """把站点数据目录指向临时目录,避免 lifespan 的 init_db 触碰真实库。""" from auth_system import db as auth_db data_dir = tmp_path / 'data' data_dir.mkdir(parents=True, exist_ok=True) for mod in (auth_db, core_db): monkeypatch.setattr(mod, 'DATA_DIR', data_dir, raising=False) monkeypatch.setattr(mod, 'DB_PATH', data_dir / 'site.db', raising=False) monkeypatch.setattr(auth_db, 'BACKUP_DIR', data_dir / 'backups', raising=False) monkeypatch.setattr(auth_db, 'SECRET_KEY_PATH', data_dir / 'secret.key', raising=False) return data_dir def test_start_task_monitor_is_idempotent(monkeypatch): """重复调用只启动一次(空转的 loop 替身,避免真的跑监控循环)。""" monkeypatch.setattr(services_tasks, '_MONITOR_STARTED', False) monkeypatch.setattr(services_tasks, '_task_monitor_loop', lambda: None) assert services_tasks.start_task_monitor() is True assert services_tasks.start_task_monitor() is False def test_lifespan_starts_monitor(monkeypatch, tmp_path): """应用 lifespan 启动监控线程(该启动点曾在迁移中丢失)。""" from web.app import create_app _isolate_data_dir(monkeypatch, tmp_path) calls: list[int] = [] monkeypatch.setattr( services_tasks, 'start_task_monitor', lambda: calls.append(1) or True) with TestClient(create_app()) as client: assert client.get('/health').status_code == 200 assert calls == [1] def test_lifespan_can_skip_monitor(monkeypatch, tmp_path): """start_monitor=False 时不启动(供测试或嵌入式构建使用)。""" from web.app import create_app _isolate_data_dir(monkeypatch, tmp_path) calls: list[int] = [] monkeypatch.setattr( services_tasks, 'start_task_monitor', lambda: calls.append(1) or True) with TestClient(create_app(start_monitor=False)) as client: assert client.get('/health').status_code == 200 assert calls == []