133 lines
4.7 KiB
Python
133 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Unit tests for ``_migrateMandateNameLabelSlugRules`` in interfaceBootstrap.
|
|
|
|
Covers:
|
|
- legacy ``name``/``label`` rows get fixed (label fill, slug rename),
|
|
- collisions across legacy rows resolve via -2/-3 suffixes in stable id order,
|
|
- valid rows are left untouched (idempotency),
|
|
- second invocation is a no-op.
|
|
"""
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
import pytest
|
|
|
|
from modules.datamodels.datamodelUam import Mandate
|
|
from modules.interfaces.interfaceBootstrap import _migrateMandateNameLabelSlugRules
|
|
from modules.shared.mandateNameUtils import isValidMandateName
|
|
|
|
|
|
class _FakeDb:
|
|
"""Minimal connector simulating getRecordset(Mandate)+recordModify(Mandate, id, data)."""
|
|
|
|
def __init__(self, rows: List[Dict[str, Any]]):
|
|
self.rows: List[Dict[str, Any]] = [dict(r) for r in rows]
|
|
self.modifyCalls: List[Dict[str, Any]] = []
|
|
|
|
def getRecordset(self, model, recordFilter: Optional[Dict[str, Any]] = None):
|
|
if model is not Mandate:
|
|
return []
|
|
if not recordFilter:
|
|
return [dict(r) for r in self.rows]
|
|
out = []
|
|
for r in self.rows:
|
|
if all(r.get(k) == v for k, v in recordFilter.items()):
|
|
out.append(dict(r))
|
|
return out
|
|
|
|
def recordModify(self, model, recordId: str, data: Dict[str, Any]):
|
|
self.modifyCalls.append({"id": str(recordId), "data": dict(data)})
|
|
for r in self.rows:
|
|
if str(r.get("id")) == str(recordId):
|
|
r.update(data)
|
|
return r
|
|
return None
|
|
|
|
|
|
def _row(mid: str, name: Any, label: Any = None) -> Dict[str, Any]:
|
|
return {"id": mid, "name": name, "label": label}
|
|
|
|
|
|
class TestMigrationFillsLabel:
|
|
def test_emptyLabelGetsNameAsLabel(self):
|
|
db = _FakeDb([_row("a1", "good-name", None)])
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
assert db.rows[0]["label"] == "good-name"
|
|
assert db.rows[0]["name"] == "good-name"
|
|
|
|
def test_emptyLabelAndEmptyNameFallsBackToMandate(self):
|
|
db = _FakeDb([_row("a1", "", "")])
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
assert db.rows[0]["label"] == "Mandate"
|
|
assert isValidMandateName(db.rows[0]["name"])
|
|
|
|
|
|
class TestMigrationRenamesInvalidNames:
|
|
def test_invalidNameGetsSlugFromLabel(self):
|
|
db = _FakeDb([_row("a1", "Home patrick", "Home Patrick")])
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
assert db.rows[0]["name"] == "home-patrick"
|
|
assert db.rows[0]["label"] == "Home Patrick"
|
|
|
|
def test_umlautsTransliterated(self):
|
|
db = _FakeDb([_row("a1", "Müller AG", "Müller AG")])
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
assert db.rows[0]["name"] == "mueller-ag"
|
|
|
|
|
|
class TestMigrationCollisions:
|
|
def test_collisionsResolveByStableIdOrder(self):
|
|
rows = [
|
|
_row("z1", "Home patrick", "Home Patrick"),
|
|
_row("a1", "home-patrick", "Home Patrick Two"),
|
|
]
|
|
db = _FakeDb(rows)
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
byId = {r["id"]: r for r in db.rows}
|
|
assert byId["a1"]["name"] == "home-patrick"
|
|
assert byId["z1"]["name"] == "home-patrick-2"
|
|
|
|
def test_threeWayCollisionGetsThirdSuffix(self):
|
|
rows = [
|
|
_row("id-aaa", "home-patrick", "Home Patrick"),
|
|
_row("id-bbb", "Home patrick", "Home Patrick"),
|
|
_row("id-ccc", "home patrick", "Home Patrick"),
|
|
]
|
|
db = _FakeDb(rows)
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
names = sorted(r["name"] for r in db.rows)
|
|
assert names == ["home-patrick", "home-patrick-2", "home-patrick-3"]
|
|
|
|
|
|
class TestMigrationIdempotency:
|
|
def test_secondRunIsNoop(self):
|
|
rows = [
|
|
_row("a1", "home-patrick", "Home Patrick"),
|
|
_row("b1", "Home Müller", ""),
|
|
]
|
|
db = _FakeDb(rows)
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
assert all(isValidMandateName(r["name"]) for r in db.rows)
|
|
firstChanges = list(db.modifyCalls)
|
|
db.modifyCalls.clear()
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
assert db.modifyCalls == [], (
|
|
f"expected no further changes after first migration, got {db.modifyCalls}; "
|
|
f"firstRun changes: {firstChanges}"
|
|
)
|
|
|
|
def test_validRowsLeftUntouched(self):
|
|
rows = [_row("a1", "root", "Root"), _row("b1", "alpina-treuhand", "Alpina Treuhand AG")]
|
|
db = _FakeDb(rows)
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
assert db.modifyCalls == []
|
|
|
|
|
|
class TestMigrationEmpty:
|
|
def test_emptyDbDoesNothing(self):
|
|
db = _FakeDb([])
|
|
_migrateMandateNameLabelSlugRules(db)
|
|
assert db.modifyCalls == []
|