From 1694dcaecea0f109487a7713e2b3fe06a2bf0dde Mon Sep 17 00:00:00 2001 From: Jose Tomas Robles Hahn Date: Mon, 30 Jun 2025 12:34:06 -0400 Subject: [PATCH] feat(dte): Add method to generate random `DteNaturalKeys` --- src/cl_sii/dte/data_models.py | 29 +++++++++++++++ src/tests/test_dte_data_models.py | 62 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/cl_sii/dte/data_models.py b/src/cl_sii/dte/data_models.py index 7abdf147..799711d0 100644 --- a/src/cl_sii/dte/data_models.py +++ b/src/cl_sii/dte/data_models.py @@ -20,6 +20,7 @@ import dataclasses import logging +import random from datetime import date, datetime from typing import Mapping, Optional, Sequence @@ -206,6 +207,34 @@ def validate_folio(cls, v: object) -> object: validate_dte_folio(v) return v + ############################################################################ + # class methods + ############################################################################ + + @classmethod + def random( + cls, + emisor_rut: Optional[Rut] = None, + tipo_dte: TipoDte | Sequence[TipoDte] = tuple(TipoDte), + folio: Optional[int] = None, + ) -> Self: + """ + Generate random DTE natural key within valid ranges. + + :param emisor_rut: RUT of the "emisor" of the DTE. If `None`, a random RUT is generated. + :param tipo_dte: The kind of DTE. If a sequence is provided, a random one is chosen. + :param folio: The sequential number of the DTE. If `None`, a random folio is generated. + """ + if emisor_rut is None: + emisor_rut = Rut.random() + if isinstance(tipo_dte, Sequence): + tipo_dte = random.choice(tipo_dte) + if folio is None: + folio = random.randint( + constants.DTE_FOLIO_FIELD_MIN_VALUE, constants.DTE_FOLIO_FIELD_MAX_VALUE + ) + return cls(emisor_rut, tipo_dte, folio) + @pydantic.dataclasses.dataclass( frozen=True, diff --git a/src/tests/test_dte_data_models.py b/src/tests/test_dte_data_models.py index d88b48a9..32d03b30 100644 --- a/src/tests/test_dte_data_models.py +++ b/src/tests/test_dte_data_models.py @@ -93,6 +93,68 @@ def test_as_dict(self) -> None: def test_slug(self) -> None: self.assertEqual(self.dte_nk_1.slug, '76354771-K--33--170') + def test_random(self) -> None: + # Test that random() returns a DteNaturalKey instance + random_dte_nk = DteNaturalKey.random() + self.assertIsInstance(random_dte_nk, DteNaturalKey) + + # Test with default parameters - + # tipo_dte should be randomly selected from all TipoDte values + random_dte_nk_default = DteNaturalKey.random() + self.assertIsInstance(random_dte_nk_default.tipo_dte, TipoDte) + self.assertIn(random_dte_nk_default.tipo_dte, TipoDte) + + # Test that each call to random() returns different values + random_dte_nk_1 = DteNaturalKey.random() + random_dte_nk_2 = DteNaturalKey.random() + self.assertNotEqual(random_dte_nk_1, random_dte_nk_2) + + # Test that the generated folio values are within valid ranges + self.assertGreaterEqual(random_dte_nk.folio, DTE_FOLIO_FIELD_MIN_VALUE) + self.assertLessEqual(random_dte_nk.folio, DTE_FOLIO_FIELD_MAX_VALUE) + + # Test that emisor_rut is a valid Rut instance + self.assertIsInstance(random_dte_nk.emisor_rut, Rut) + + # Test that tipo_dte is a valid TipoDte enum value + self.assertIsInstance(random_dte_nk.tipo_dte, TipoDte) + self.assertIn(random_dte_nk.tipo_dte, TipoDte) + + # Test with custom parameters + custom_rut = Rut('12345678-9') + custom_tipo = TipoDte.NOTA_CREDITO_ELECTRONICA + custom_folio = 12345 + + custom_dte_nk = DteNaturalKey.random( + emisor_rut=custom_rut, tipo_dte=custom_tipo, folio=custom_folio + ) + + self.assertEqual(custom_dte_nk.emisor_rut, custom_rut) + self.assertEqual(custom_dte_nk.tipo_dte, custom_tipo) + self.assertEqual(custom_dte_nk.folio, custom_folio) + + # Test with specific tipo_dte (single value, not sequence) + specific_tipo = TipoDte.FACTURA_ELECTRONICA + specific_dte_nk = DteNaturalKey.random(tipo_dte=specific_tipo) + self.assertEqual(specific_dte_nk.tipo_dte, specific_tipo) + + # Test with partial custom parameters + partial_custom = DteNaturalKey.random(emisor_rut=custom_rut) + self.assertEqual(partial_custom.emisor_rut, custom_rut) + self.assertIsInstance(partial_custom.tipo_dte, TipoDte) + self.assertGreaterEqual(partial_custom.folio, DTE_FOLIO_FIELD_MIN_VALUE) + self.assertLessEqual(partial_custom.folio, DTE_FOLIO_FIELD_MAX_VALUE) + + # Test that multiple calls generate different combinations + generated_combinations = set() + for _ in range(10): + dte_nk = DteNaturalKey.random() + combination = (dte_nk.emisor_rut, dte_nk.tipo_dte, dte_nk.folio) + generated_combinations.add(combination) + + # Should generate mostly unique combinations (allow some duplicates due to randomness) + self.assertGreater(len(generated_combinations), 1) + class DteDataL0Test(unittest.TestCase): def setUp(self) -> None: