"""同步控制台的真实鉴权、配置保存、审计与进度契约。仅隔离库执行。""" from pathlib import Path from uuid import uuid4 import pytest from sqlalchemy import select from app.core.db import get_session_factory from app.models import OperationLog, TPlusSyncBatch from app.services.tplus import task_service pytestmark = pytest.mark.integration BASE = "/api/v1/system/tplus" def test_不可逆迁移拒绝回退且不修改同步批次(): from alembic import command from alembic.config import Config from alembic.script import ScriptDirectory from sqlalchemy import inspect, text from app.core.db import get_engine engine = get_engine() assert engine.url.database.startswith("payroll_test_") with engine.connect() as connection: before = connection.exec_driver_sql('SELECT COUNT(*) FROM tplus_sync_batches').scalar_one() config = Config(str(Path(__file__).resolve().parents[2] / "alembic.ini")) with pytest.raises(RuntimeError, match="不可直接回退"): command.downgrade(config, "26e7f8a9b0c1") command.upgrade(config, "head") assert inspect(engine).has_table("tplus_automation_configs") with engine.connect() as connection: assert connection.execute(text('SELECT version_num FROM alembic_version')).scalar_one() == ScriptDirectory.from_config(config).get_current_head() assert connection.exec_driver_sql('SELECT COUNT(*) FROM tplus_sync_batches').scalar_one() == before def test_开关保存后刷新生效且拒绝过期版本(client, admin, normal, tplus_tasks, monkeypatch): settings = task_service.get_settings().model_copy(update={ "t_plus_database": "unit_auto_" + uuid4().hex[:8], "t_plus_automation_enabled": True}) monkeypatch.setattr(task_service, "get_settings", lambda: settings) initial = client.get(BASE + "/overview", headers=admin["headers"]).json()["data"]["automation"] assert initial["enabled"] and initial["editable"] and initial["version"] == 0 payload = {"enabled": False, "expected_version": 0} assert client.put(BASE + "/automation", json=payload).status_code == 401 assert client.put(BASE + "/automation", json=payload, headers=normal["headers"]).status_code == 403 saved = client.put(BASE + "/automation", json=payload, headers=admin["headers"]) assert saved.status_code == 200 and not saved.json()["data"]["enabled"] assert saved.json()["data"]["version"] == 1 refreshed = client.get(BASE + "/overview", headers=admin["headers"]).json()["data"]["automation"] assert refreshed["enabled"] is False and refreshed["configuration_origin"] == "SAVED" assert client.put(BASE + "/automation", json={"enabled": True, "expected_version": 0}, headers=admin["headers"]).status_code == 409 assert client.put(BASE + "/automation", json={"enabled": "false", "expected_version": 1}, headers=admin["headers"]).status_code == 400 with get_session_factory()() as session: assert session.scalar(select(OperationLog.id).where( OperationLog.user_id == admin["id"], OperationLog.action == "修改自动采集", OperationLog.success == 1, ).limit(1)) def test_列表详情概况的进度一致且不透传原始报告(client, admin, tplus_tasks): number = client.post(BASE + "/batches", json=tplus_tasks(), headers=admin["headers"]).json()["data"]["batch_no"] with get_session_factory()() as session: batch = session.scalar(select(TPlusSyncBatch).where(TPlusSyncBatch.batch_no == number)) batch.status, batch.source_record_count, batch.skipped_record_count = "RUNNING", 50, 50 batch.audit_result_json = {"password": "private-string-not-exposed", "progress": { "phase": "READING", "total_records": 200, "active_entity": "personnel", "entities": {"personnel": {"total": 200, "processed": 50, "status": "READING"}}, }} session.commit() for path in (f"/batches/{number}", "/batches", "/overview"): response = client.get(BASE + path, headers=admin["headers"]) assert response.status_code == 200 assert "private-string-not-exposed" not in response.text data = response.json()["data"] batch = data["current_batch"] if path == "/overview" else next(b for b in data["list"] if b["batch_no"] == number) if path == "/batches" else data assert batch["progress"]["percentage"] == 25 assert batch["progress"]["processed_records"] == 50