aps-agent/tests/golden/test_db_projection.py

119 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# M-A 黄金测试:SQLite 主数据入库 → 投影回 world → PoolEngine 可排
# ============================================================
from __future__ import annotations
import pytest
from server.engines import PoolEngine
from server.state.seed import seed_world
@pytest.fixture()
def db_env(tmp_path, monkeypatch):
"""隔离数据库:每个用例独立 master.db。"""
monkeypatch.setenv("APS_DB_PATH", str(tmp_path / "master.db"))
monkeypatch.delenv("APS_DB_DISABLED", raising=False)
from server.db.database import reset_engine
reset_engine()
yield
reset_engine()
def test_world_to_db_and_projection_roundtrip(db_env):
from server.db.sync import db_has_master, db_to_world, world_to_db
world = seed_world()
counts = world_to_db(world)
assert counts["materials"] == len(world["materials"])
assert counts["flexRoutings"] == len(world["flexRoutings"])
assert counts["flexParams"] == 1
assert db_has_master()
# 投影到空 world:主数据键零损耗还原
shadow: dict = {}
back = db_to_world(shadow)
assert back["materials"] == len(world["materials"])
assert shadow["materials"] == world["materials"]
assert shadow["flexRoutings"] == world["flexRoutings"]
assert shadow["salesOrders"] == world["salesOrders"]
assert shadow["flexParams"] == world["flexParams"]
def test_projection_world_schedulable_by_pool_engine(db_env):
from server.db.sync import db_to_world, world_to_db
world = seed_world()
world_to_db(world)
# 投影世界补运行态骨架后可直接排产
shadow = seed_world()
for k in ("flexScheduleVersions", "flexVirtualLines", "flexWorkOrders", "flexConflicts"):
shadow[k] = []
db_to_world(shadow)
counters: dict[str, int] = {}
def nid(kind: str) -> int:
counters[kind] = counters.get(kind, 0) + 1
return counters[kind]
result = PoolEngine().solve(shadow, nid, sort_mode="BOTTLENECK")
assert result["orderCount"] > 0
assert result["woCount"] > 0
def test_multi_project_isolation(db_env):
from server.db.sync import (
db_to_world, get_active_project, list_projects,
set_active_project, world_to_db,
)
world = seed_world()
world_to_db(world) # 写入默认 demo 项目
assert get_active_project()["code"] == "demo"
# 切到康尼项目:空主数据,互不污染
set_active_project("kangni", "康尼机电")
shadow: dict = {}
assert db_to_world(shadow) == {}
world_to_db({"materials": [{"id": 1, "code": "KN-001", "name": "康尼物料"}]})
kn: dict = {}
db_to_world(kn)
assert [m["code"] for m in kn["materials"]] == ["KN-001"]
# 切回 demo:原数据完好
set_active_project("demo")
demo: dict = {}
db_to_world(demo)
assert len(demo["materials"]) == len(world["materials"])
assert {p["code"] for p in list_projects()} >= {"demo", "kangni"}
def test_demo_pack_replay_and_date_rebase(tmp_path):
from server.state.packs import export_pack, load_pack
world = seed_world()
pack_path = str(tmp_path / "demo.json")
export_pack(world, pack_path, name="演示")
# 伪造 30 天前生成的包 → 加载后日期整体平移 30 天
import json
with open(pack_path, "r", encoding="utf-8") as f:
pack = json.load(f)
from datetime import datetime, timedelta
pack["baseDate"] = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")
with open(pack_path, "w", encoding="utf-8") as f:
json.dump(pack, f, ensure_ascii=False)
loaded = load_pack(pack_path)
def shift(day: str, days: int) -> str:
return (datetime.strptime(day, "%Y-%m-%d") + timedelta(days=days)).strftime("%Y-%m-%d")
# 30 天前的包 → 全部日期前移 30 天对齐「今天」
assert loaded["salesOrders"][0]["deliveryDate"] == shift(world["salesOrders"][0]["deliveryDate"], 30)
assert loaded["flexOrders"][0]["dueDate"] == shift(world["flexOrders"][0]["dueDate"], 30)
assert len(loaded["materials"]) == len(world["materials"])