补齐源快照、资料预演、正式导入及工资核算的依赖链路。 新增版本化输入、逐段 FIFO、生成锁和结构化当月调整, 并提供默认关闭的周期采集、有限重试及 Windows 托管。 原始客户资料、私有配置及首页改动不纳入本次提交。 BREAKING CHANGE: 升级前需应用新增迁移,并通过 PAYROLL_MAKER_USER_ID 绑定实际工资制作人,否则业务只读。 旧单批次核算默认关闭;历史兼容需显式启用 PAYROLL_ALLOW_LEGACY_CALCULATION。生产启用和真实金额待验收。
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
"""T+ 配置和读取适配层的纯单元测试。"""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from sqlalchemy.dialects.mssql.pyodbc import MSDialect_pyodbc
|
|
|
|
from app.core.config import Settings
|
|
from app.services.tplus.mapping import MappingStatus, get_mapping
|
|
from app.services.tplus.reader import TPlusReader
|
|
|
|
|
|
def _settings(**overrides: object) -> Settings:
|
|
params: dict[str, object] = {"_env_file": None, "app_secret_key": "x" * 32}
|
|
params.update(overrides)
|
|
return Settings(**params) # type: ignore[arg-type]
|
|
|
|
|
|
def test_T加数据库URL使用mssql_pyodbc并隐藏密码() -> None:
|
|
settings = _settings(
|
|
t_plus_enabled=True,
|
|
t_plus_database="UFTData472115_000001",
|
|
t_plus_user="reader",
|
|
t_plus_password="p@ss:w/ord",
|
|
)
|
|
url = settings.t_plus_database_url
|
|
assert url.drivername == "mssql+pyodbc"
|
|
assert url.database == "UFTData472115_000001"
|
|
assert "p@ss:w/ord" not in url.render_as_string()
|
|
|
|
|
|
def test_T加未启用时读取器不建立连接() -> None:
|
|
settings = _settings(t_plus_enabled=False)
|
|
assert settings.t_plus_enabled is False
|
|
|
|
|
|
def test_T加默认加密且允许显式隔离环境回退(monkeypatch) -> None:
|
|
monkeypatch.delenv("T_PLUS_ENCRYPT", raising=False)
|
|
monkeypatch.delenv("T_PLUS_TRUST_SERVER_CERTIFICATE", raising=False)
|
|
assert _settings().t_plus_database_url.query["Encrypt"] == "yes"
|
|
assert _settings().t_plus_database_url.query["TrustServerCertificate"] == "yes"
|
|
url = _settings(t_plus_encrypt=False, t_plus_trust_server_certificate=False).t_plus_database_url
|
|
assert url.query["Encrypt"] == "no"
|
|
assert url.query["TrustServerCertificate"] == "no"
|
|
|
|
|
|
@pytest.mark.parametrize("value", ["a;b", "a}b", "{prefix", "中文;UID=other}x"])
|
|
def test_ODBC特殊字符保持字段边界且不泄露密码(value) -> None:
|
|
settings = _settings(
|
|
t_plus_odbc_driver="Driver}Name",
|
|
t_plus_database=value,
|
|
t_plus_user=value,
|
|
t_plus_password=value,
|
|
t_plus_encrypt=True,
|
|
)
|
|
url = settings.t_plus_database_url
|
|
args, _ = MSDialect_pyodbc().create_connect_args(url)
|
|
escaped = "{" + value.replace("}", "}}") + "}"
|
|
assert f"Database={escaped};UID={escaped};PWD={escaped};" in args[0]
|
|
assert "DRIVER={Driver}}Name}" in args[0]
|
|
assert ";Encrypt=yes;" in args[0]
|
|
assert url.render_as_string().split("@", 1)[0].endswith(":***")
|
|
|
|
|
|
def test_T加查询文件覆盖六类实体() -> None:
|
|
reader = TPlusReader.__new__(TPlusReader)
|
|
reader.query_dir = Path(__file__).resolve().parents[3] / "db" / "queries" / "tplus"
|
|
assert reader.inventory() == (
|
|
"personnel",
|
|
"products",
|
|
"customers",
|
|
"businesses",
|
|
"shipments",
|
|
"receipts",
|
|
)
|
|
for entity in reader.inventory():
|
|
assert (reader.query_dir / f"{entity}.sql").is_file()
|
|
|
|
|
|
def test_T加高风险映射保持待确认() -> None:
|
|
assert get_mapping("businesses").status is MappingStatus.PENDING
|
|
assert "指导价取值时点" in get_mapping("businesses").unresolved
|
|
|
|
|
|
def test_未知T加实体被拒绝() -> None:
|
|
with pytest.raises(KeyError):
|
|
get_mapping("unknown")
|