109 lines
4.4 KiB
Python
109 lines
4.4 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Integration tests for the slug-derivation contract that
|
|
``AppObjects._provisionMandateForUser`` relies on.
|
|
|
|
Covers AC#10 from ``wiki/c-work/1-plan/2026-04-mandate-name-label-logic.md``:
|
|
auto-provisioning a user named "Patrick.Möller" yields
|
|
``label = "Home Patrick.Möller"`` and ``name = "home-patrick-moeller"``
|
|
(or ``-2``, ``-3``, ... on collisions).
|
|
|
|
The full ``_provisionMandateForUser`` flow has many side effects (subscriptions,
|
|
billing, feature instances). For unit-level integration we focus on the
|
|
slug-allocation contract via ``_generateUniqueMandateName`` — that is the
|
|
single new behaviour the provisioning method delegates to.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
from modules.datamodels.datamodelUam import Mandate
|
|
from modules.interfaces.interfaceDbApp import AppObjects
|
|
|
|
|
|
class _FakeDb:
|
|
def __init__(self, rows: Optional[List[Dict[str, Any]]] = None):
|
|
self.rows: List[Dict[str, Any]] = [dict(r) for r in (rows or [])]
|
|
|
|
def getRecordset(self, model, recordFilter: Optional[Dict[str, Any]] = None):
|
|
if model is not Mandate:
|
|
return []
|
|
return [dict(r) for r in self.rows]
|
|
|
|
|
|
def _buildInterface(rows: Optional[List[Dict[str, Any]]] = None) -> AppObjects:
|
|
iface = AppObjects.__new__(AppObjects)
|
|
iface.db = _FakeDb(rows)
|
|
iface.currentUser = Mock(id="u-1", isPlatformAdmin=True, isSysAdmin=False)
|
|
iface.userId = "u-1"
|
|
iface.mandateId = None
|
|
iface.featureInstanceId = None
|
|
iface.rbac = Mock()
|
|
return iface
|
|
|
|
|
|
class TestProvisioningSlugFromHomeLabel:
|
|
def test_simpleHomeLabel(self):
|
|
iface = _buildInterface()
|
|
assert iface._generateUniqueMandateName("Home patrick") == "home-patrick"
|
|
|
|
def test_umlautPersonNameTransliterated(self):
|
|
"""AC#10: Patrick.Möller → home-patrick-moeller"""
|
|
iface = _buildInterface()
|
|
result = iface._generateUniqueMandateName("Home Patrick.Möller")
|
|
assert result == "home-patrick-moeller"
|
|
|
|
def test_eszettAndUmlautsAndDots(self):
|
|
iface = _buildInterface()
|
|
result = iface._generateUniqueMandateName("Home Müßler.Ümpf")
|
|
assert result == "home-muessler-uempf"
|
|
|
|
def test_emptyLabelFallsBackToFallbackSlug(self):
|
|
iface = _buildInterface()
|
|
result = iface._generateUniqueMandateName("")
|
|
assert result == "mn"
|
|
|
|
|
|
class TestProvisioningSlugCollisions:
|
|
def test_secondHomeWithSameLabelGetsSuffix(self):
|
|
rows = [{"id": "first", "name": "home-patrick-moeller", "label": "Home Patrick.Möller"}]
|
|
iface = _buildInterface(rows)
|
|
result = iface._generateUniqueMandateName("Home Patrick.Möller")
|
|
assert result == "home-patrick-moeller-2"
|
|
|
|
def test_thirdCollisionGetsThirdSuffix(self):
|
|
rows = [
|
|
{"id": "first", "name": "home-patrick-moeller", "label": "Home Patrick.Möller"},
|
|
{"id": "second", "name": "home-patrick-moeller-2", "label": "Home Patrick.Möller"},
|
|
]
|
|
iface = _buildInterface(rows)
|
|
result = iface._generateUniqueMandateName("Home Patrick.Möller")
|
|
assert result == "home-patrick-moeller-3"
|
|
|
|
def test_excludeIdHonored(self):
|
|
"""When updating, the row being updated must not collide with itself."""
|
|
rows = [{"id": "self", "name": "home-patrick-moeller", "label": "Home Patrick.Möller"}]
|
|
iface = _buildInterface(rows)
|
|
result = iface._generateUniqueMandateName("Home Patrick.Möller", excludeId="self")
|
|
assert result == "home-patrick-moeller", "own row should be excluded from collision check"
|
|
|
|
|
|
class TestProvisioningPlanGuard:
|
|
"""Sanity guard: the new label-mandatory check fires before any DB write."""
|
|
|
|
def test_emptyLabelRejected(self):
|
|
iface = _buildInterface()
|
|
with pytest.raises(ValueError) as excInfo:
|
|
iface._provisionMandateForUser(userId="u-1", mandateLabel="", planKey="TRIAL_14D")
|
|
assert "label" in str(excInfo.value).lower() or "voller name" in str(excInfo.value).lower()
|
|
|
|
def test_unknownPlanRejectedBeforeLabelCheck(self):
|
|
iface = _buildInterface()
|
|
with pytest.raises(ValueError) as excInfo:
|
|
iface._provisionMandateForUser(userId="u-1", mandateLabel="Home X", planKey="DOES_NOT_EXIST")
|
|
assert "plan" in str(excInfo.value).lower()
|