"""排队任务要有实际执行者;忙锁、断连和进程中断不能被误记成成功。""" from contextlib import contextmanager, nullcontext from unittest.mock import Mock from uuid import uuid4 from sqlalchemy import select from app.core.tplus_lock import TPlusBusyError from app.models import TPlusSyncError from app.schemas.tplus import TPlusCreateIn from app.services.tplus import task_service, worker def _queued(session, audit=False): return task_service.create_task( session, TPlusCreateIn( request_key=uuid4(), task_type="AUDIT" if audit else "READ", entity_types=[] if audit else ["products"], ), ) def _worker_setup(monkeypatch): monkeypatch.setattr(worker, "sync_lock", lambda session: nullcontext()) monkeypatch.setattr( "app.services.tplus.reader.TPlusReader.iter_entity", lambda *a, **kw: iter([{"id": 1, "code": "A"}]), ) return lambda: nullcontext(Mock()) def test_读取任务由worker处理并可观察重复数(tplus_session, monkeypatch): source = _worker_setup(monkeypatch) first = _queued(tplus_session) assert worker.run_once(tplus_session, source_factory=source) == first.batch_no assert first.status == "SUCCEEDED" and first.started_at is not None second = _queued(tplus_session) worker.run_once(tplus_session, source_factory=source) assert second.staged_record_count == 0 assert second.skipped_record_count == 1 def test_忙锁时不领取任务也不恢复其他运行记录(tplus_session, monkeypatch): source = _worker_setup(monkeypatch) batch = _queued(tplus_session) @contextmanager def busy(_session): raise TPlusBusyError("已有任务") yield monkeypatch.setattr(worker, "sync_lock", busy) assert worker.run_once(tplus_session, source_factory=source) is None assert batch.status == "QUEUED" def test_源连接失败写入固定错误不泄漏凭据(tplus_session, monkeypatch): _worker_setup(monkeypatch) batch = _queued(tplus_session) def unavailable(): raise RuntimeError("password=private-secret") worker.run_once(tplus_session, source_factory=unavailable) assert batch.status == "FAILED" error = tplus_session.scalar(select(TPlusSyncError)) assert "private-secret" not in error.error_message assert error.error_code == "EXECUTION_ERROR" def test_取得锁后把遗留运行中任务记为中断失败(tplus_session, monkeypatch): source = _worker_setup(monkeypatch) batch = _queued(tplus_session) batch.status = "RUNNING" tplus_session.commit() assert worker.run_once(tplus_session, source_factory=source) is None assert batch.status == "FAILED" assert tplus_session.scalar(select(TPlusSyncError.error_code)) == "INTERRUPTED" def test_核验任务持久化报告但不修改业务数据(tplus_session, monkeypatch): source = _worker_setup(monkeypatch) batch = _queued(tplus_session, audit=True) monkeypatch.setattr( worker.TPlusRelationshipAudit, "run", lambda self: { "relationship_status": "OBSERVED", "receipt_kinds": [{"row_count": 10}], "compatibility_status": "PENDING", "is_target_engine": False, }, ) worker.run_once(tplus_session, source_factory=source) assert batch.status == "SUCCEEDED" assert batch.audit_result_json["compatibility_status"] == "PENDING" assert batch.staged_record_count == 0