#!/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 == []