"""双时态集成测试(需 PostgreSQL)。 验证 R3:按历史业务时点回放属性值,以及双时态排他约束防止有效期重叠。 """ from __future__ import annotations import datetime as dt import pytest from sqlalchemy.exc import IntegrityError from app.datahub import bitemporal_repo as btr from app.datahub.graph_repo import upsert_entity from app.datahub.ontology import EntityType def test_bitemporal_replay(session): """不同业务时点回放出不同的属性值。""" cust = upsert_entity(session, EntityType.CUSTOMER, "CUST_BT", "丁公司") session.flush() t1 = dt.datetime(2025, 1, 1, tzinfo=dt.UTC) t2 = dt.datetime(2025, 6, 1, tzinfo=dt.UTC) btr.record_fact(session, cust.id, "credit_level", {"v": "A"}, valid_from=t1, valid_to=t2) btr.record_fact(session, cust.id, "credit_level", {"v": "C"}, valid_from=t2) session.flush() early = btr.as_of(session, cust.id, "credit_level", dt.datetime(2025, 3, 1, tzinfo=dt.UTC)) late = btr.as_of(session, cust.id, "credit_level", dt.datetime(2025, 9, 1, tzinfo=dt.UTC)) assert early is not None and early.attr_value["v"] == "A" assert late is not None and late.attr_value["v"] == "C" def test_bitemporal_exclusion_constraint(session): """同一实体同一属性的业务有效期重叠应被排他约束拒绝。""" cust = upsert_entity(session, EntityType.CUSTOMER, "CUST_EX", "戊公司") session.flush() t1 = dt.datetime(2025, 1, 1, tzinfo=dt.UTC) t3 = dt.datetime(2025, 12, 1, tzinfo=dt.UTC) t2 = dt.datetime(2025, 6, 1, tzinfo=dt.UTC) btr.record_fact(session, cust.id, "status", {"v": "active"}, valid_from=t1, valid_to=t3) session.flush() # 与上一条 [t1,t3) 重叠:record_fact 内部 flush 时即触发排他约束 with pytest.raises(IntegrityError): btr.record_fact(session, cust.id, "status", {"v": "frozen"}, valid_from=t2, valid_to=None)