"""Unit tests: variable engine, invoice numbers, random financial generators.""" import string import pytest from app.services.variable_engine import ( GeneratedValues, VariableEngine, analyze_template_variables, ) class TestInvoiceGeneration: def test_default_format_shape(self): ve = VariableEngine() inv = ve.generate_invoice_number() assert inv.startswith("INV-") # {R4}-{R5}-{R6}-{seq4}: total = 4+1+5+1+6+1+4 = 22 chars assert len(inv) == len("INV-") + 22 parts = inv[len("INV-"):].split("-") assert len(parts) == 4 for p in parts: assert p, "empty block" for ch in parts[0]: assert ch in string.ascii_uppercase + string.digits def test_crypto_strong_distinct_values(self): ve = VariableEngine() seen = set() for _ in range(200): seen.add(ve.generate_invoice_number(used=seen)) assert len(seen) == 200 def test_collision_checking_avoids_used(self): ve = VariableEngine({"invoice_format": "{d2}"}) # tiny space: 100 values used = set() for _ in range(100): used.add(ve.generate_invoice_number(used=used)) assert len(used) == 100 with pytest.raises(ValueError): ve.generate_invoice_number(used=used) def test_custom_prefix_and_format(self): ve = VariableEngine({"invoice_prefix": "ACME-", "invoice_format": "{prefix}{r6}"}) inv = ve.generate_invoice_number() assert inv.startswith("ACME-") body = inv[len("ACME-"):] assert len(body) == 6 for ch in body: assert ch in string.ascii_lowercase + string.digits def test_date_token(self): ve = VariableEngine({"invoice_format": "{date}"}) inv = ve.generate_invoice_number() assert len(inv) == 8 and inv.isdigit() def test_hash_token(self): ve = VariableEngine({"invoice_format": "{hash4}"}) inv = ve.generate_invoice_number() # token_hex(4) -> 8 hex chars ---ENGINE--- text: str unknown: list = field(default_factory=list) missing: list = field(default_factory=list) # known but no data available used: list = field(default_factory=list) class VariableEngine: def __init__(self, config: Optional[dict] = None, rng=None): self.config = config or {} self.rng = rng or secrets.SystemRandom() # ------------------------------------------------------------------ helpers def _cfg(self, key: str, default): return self.config.get(key, default) def generate_invoice_number(self, used: set = None) -> str: """Generate a unique invoice/reference number using configurable format tokens. Tokens: {prefix} {R#} uppercase {r#} lowercase {d#} digits {seq4} {date} {hash} Cryptographically strong randomness via SystemRandom. """ used = used if used is not None else set() fmt = self._cfg("invoice_format", "{prefix}{R4}-{R5}-{R6}-{seq4}") prefix = self._cfg("invoice_prefix", "INV-") for _ in range(50): val = self._render_format(fmt, prefix) if val not in used: return val raise ValueError("Could not generate unique invoice number after 50 attempts") def _render_format(self, fmt: str, prefix: str) -> str: from string import ascii_letters, digits upper = string.ascii_uppercase + string.digits lower = string.ascii_lowercase + string.digits digits_only = string.digits