""" UserRecord dataclass — in-memory representation of a local-identity user. Schema fields (all stored in YAML): schema_version str — format version, currently "1" username str — Linux username (primary) or derived username (test) fullname str — display name email str — contact email environment str — always "local"; production connectors reject this value generated bool — True for auto-generated test users source_user str? — for generated users: the source username production_identity — optional mapping to a production account .username str .realm str """ from __future__ import annotations from dataclasses import dataclass, field from typing import Optional import yaml @dataclass class ProductionIdentity: username: str realm: str @dataclass class UserRecord: username: str fullname: str email: str schema_version: str = "1" environment: str = "local" generated: bool = False source_user: Optional[str] = None production_identity: Optional[ProductionIdentity] = None # ------------------------------------------------------------------ # # Serialisation # # ------------------------------------------------------------------ # def to_dict(self) -> dict: d: dict = { "schema_version": self.schema_version, "username": self.username, "fullname": self.fullname, "email": self.email, "environment": self.environment, "generated": self.generated, } if self.source_user is not None: d["source_user"] = self.source_user if self.production_identity is not None: d["production_identity"] = { "username": self.production_identity.username, "realm": self.production_identity.realm, } return d def to_yaml(self) -> str: return yaml.dump( self.to_dict(), default_flow_style=False, allow_unicode=True, sort_keys=False, ) @classmethod def from_dict(cls, data: dict) -> UserRecord: pi: Optional[ProductionIdentity] = None if data.get("production_identity"): pi = ProductionIdentity(**data["production_identity"]) return cls( schema_version=data.get("schema_version", "1"), username=data["username"], fullname=data["fullname"], email=data["email"], environment=data.get("environment", "local"), generated=data.get("generated", False), source_user=data.get("source_user"), production_identity=pi, ) @classmethod def from_yaml(cls, text: str) -> UserRecord: return cls.from_dict(yaml.safe_load(text)) # ------------------------------------------------------------------ # # Test-user derivation (pure function) # # ------------------------------------------------------------------ # def make_test_user(primary: UserRecord, n: int) -> UserRecord: """ Derive test user N from the primary user. Derivation rules: username → fullname → +test email → +test@ (or +test if no @) """ if n < 1: raise ValueError(f"n must be >= 1, got {n}") suffix = str(n) test_username = primary.username + suffix test_fullname = f"{primary.fullname}+test{suffix}" if "@" in primary.email: local, domain = primary.email.rsplit("@", 1) test_email = f"{local}+test{suffix}@{domain}" else: test_email = f"{primary.email}+test{suffix}" return UserRecord( username=test_username, fullname=test_fullname, email=test_email, environment="local", generated=True, source_user=primary.username, )