FUE-T04: add Meter.uuid (stable HA identity anchor) + backfill migration
This commit is contained in:
@@ -798,7 +798,7 @@ def test_meter_columns(energy_db):
|
||||
inspector = inspect(energy_db)
|
||||
columns = {col["name"]: col for col in inspector.get_columns("meter")}
|
||||
|
||||
non_nullable = {"id", "label", "commodity", "started_at", "reason", "created_at"}
|
||||
non_nullable = {"id", "uuid", "label", "commodity", "started_at", "reason", "created_at"}
|
||||
nullable = {"ended_at", "note"}
|
||||
|
||||
for col_name in non_nullable:
|
||||
@@ -810,11 +810,20 @@ def test_meter_columns(energy_db):
|
||||
assert columns[col_name]["nullable"], f"{col_name} should be nullable"
|
||||
|
||||
|
||||
def test_meter_uuid_unique_constraint(energy_db):
|
||||
"""meter.uuid must have a unique constraint."""
|
||||
inspector = inspect(energy_db)
|
||||
unique_constraints = inspector.get_unique_constraints("meter")
|
||||
unique_cols = [col for uc in unique_constraints for col in uc["column_names"]]
|
||||
assert "uuid" in unique_cols, "meter.uuid must have a unique constraint"
|
||||
|
||||
|
||||
def test_meter_orm_metadata():
|
||||
"""Meter must be registered in Base.metadata with correct field types."""
|
||||
assert "meter" in Base.metadata.tables, "meter not in Base.metadata"
|
||||
table = Base.metadata.tables["meter"]
|
||||
assert "id" in table.columns
|
||||
assert "uuid" in table.columns
|
||||
assert "label" in table.columns
|
||||
assert "commodity" in table.columns
|
||||
assert "started_at" in table.columns
|
||||
@@ -824,6 +833,14 @@ def test_meter_orm_metadata():
|
||||
assert "created_at" in table.columns
|
||||
|
||||
|
||||
def test_meter_uuid_unique_in_metadata():
|
||||
"""Meter.uuid must be declared unique and not nullable in ORM metadata."""
|
||||
table = Base.metadata.tables["meter"]
|
||||
col = table.columns["uuid"]
|
||||
assert col.unique, "Meter.uuid must be declared unique in ORM metadata"
|
||||
assert not col.nullable, "Meter.uuid must be NOT NULL in ORM metadata"
|
||||
|
||||
|
||||
def test_meter_insert_and_retrieve(energy_db):
|
||||
"""A Meter row can be inserted and retrieved with all fields intact."""
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
|
||||
@@ -381,6 +381,43 @@ class TestDeclareMeter:
|
||||
assert fetched.commodity == "gas"
|
||||
assert fetched.note == "Rotameter serial XYZ"
|
||||
|
||||
def test_declare_meter_generates_uuid(self, session: Session):
|
||||
"""declare_meter must auto-generate a non-empty uuid via ORM default."""
|
||||
import re
|
||||
UUID4_RE = re.compile(
|
||||
r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
m = declare_meter(
|
||||
session,
|
||||
label="Meter with UUID",
|
||||
started_at=_T0,
|
||||
reason="initial",
|
||||
)
|
||||
session.commit()
|
||||
|
||||
fetched = session.get(Meter, m.id)
|
||||
assert fetched is not None
|
||||
assert fetched.uuid is not None, "uuid must not be None after declare_meter"
|
||||
assert fetched.uuid != "", "uuid must not be empty"
|
||||
assert UUID4_RE.match(fetched.uuid), (
|
||||
f"uuid {fetched.uuid!r} does not look like a valid UUID v4"
|
||||
)
|
||||
|
||||
def test_declare_meter_each_gets_distinct_uuid(self, session: Session):
|
||||
"""Each declared meter must receive a distinct UUID (not duplicated)."""
|
||||
m1 = declare_meter(session, label="M1", started_at=_T0, reason="initial")
|
||||
session.commit()
|
||||
|
||||
t1 = _T0 + timedelta(days=10)
|
||||
m2 = declare_meter(session, label="M2", started_at=t1, reason="meter_swap")
|
||||
session.commit()
|
||||
|
||||
assert m1.uuid != m2.uuid, (
|
||||
f"Two declared meters must have distinct UUIDs; both got {m1.uuid!r}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Different commodities are independent
|
||||
|
||||
Reference in New Issue
Block a user