"""在显式指定的临时 MySQL 库中验证工资月份行锁。""" import os from concurrent.futures import ThreadPoolExecutor, TimeoutError from datetime import datetime from threading import Event from types import SimpleNamespace import pytest from sqlalchemy import create_engine from sqlalchemy.orm import Session from app.models import PayrollMonth from app.services.payroll_months import lock_month, lock_month_row @pytest.mark.skipif(not os.environ.get("PAYROLL_LOCK_TEST_URL"), reason="需要独立的临时 MySQL 测试库") def test_同步等待工资事务提交并读取最新生成状态(monkeypatch): from app.services import payroll_months engine = create_engine(os.environ["PAYROLL_LOCK_TEST_URL"]) PayrollMonth.__table__.create(engine) monkeypatch.setattr(payroll_months, "ensure_payroll_writer", lambda _operator: None) actor = SimpleNamespace(id=1, username="test") started = Event() def check_sync(): with Session(engine) as session: started.set() control = lock_month_row(session, "2026-03", actor) return control.generated_at try: with Session(engine) as generation, ThreadPoolExecutor(max_workers=1) as pool: control = lock_month(generation, "2026-03", actor) result = pool.submit(check_sync) assert started.wait(5) with pytest.raises(TimeoutError): result.result(timeout=0.25) control.generated_at = datetime(2026, 3, 31) generation.commit() assert result.result(timeout=10) == datetime(2026, 3, 31) started.clear() def generate(): with Session(engine) as session: started.set() control = lock_month(session, "2026-04", actor) control.generated_at = datetime(2026, 4, 30) session.commit() with Session(engine) as sync, ThreadPoolExecutor(max_workers=1) as pool: lock_month_row(sync, "2026-04", actor) result = pool.submit(generate) assert started.wait(5) with pytest.raises(TimeoutError): result.result(timeout=0.25) sync.commit() result.result(timeout=10) finally: engine.dispose()