Skip to content

Commit 7ba292c

Browse files
refactor_Standardize_code_quality_fix_tests_and_archive_legacy_code
1 parent 8ab2f82 commit 7ba292c

14 files changed

Lines changed: 912 additions & 0 deletions

.github/workflows/python-ci.yml

Whitespace-only changes.

coverage.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

legacy_archive.zip

147 KB
Binary file not shown.

requirements-dev.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Herramientas de desarrollo y testing
2+
pytest>=7.4.0
3+
pytest-cov>=4.1.0
4+
pytest-mock>=3.12.0
5+
6+
# Calidad de código
7+
black>=23.0.0
8+
ruff # Reemplaza flake8 y añade más funcionalidades
9+
mypy>=1.7.0
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""Integration-style test for unified EmailServicesTask processing both sources.
2+
Simulates DB pools for 'correos' and 'tareas' without touching real Access files.
3+
"""
4+
from unittest.mock import MagicMock, patch
5+
6+
from email_services.email_task import EmailServicesTask
7+
8+
9+
class DummyPool:
10+
def __init__(self, rows):
11+
self.rows = rows
12+
self.queries = []
13+
self.updates = []
14+
15+
def execute_query(self, q): # simplistic capture
16+
self.queries.append(q)
17+
return list(self.rows)
18+
19+
def update_record(self, table, data, where):
20+
self.updates.append((table, data, where))
21+
return True
22+
23+
24+
@patch("email_services.email_manager.smtplib.SMTP")
25+
@patch("email_services.email_manager.get_tareas_connection_pool")
26+
@patch("email_services.email_manager.get_correos_connection_pool")
27+
@patch("email_services.email_manager.config")
28+
def test_email_services_task_full_cycle(
29+
mock_config, mock_get_correos_pool, mock_get_tareas_pool, mock_smtp
30+
):
31+
# Configure fake config
32+
mock_config.smtp_server = "localhost"
33+
mock_config.smtp_port = 25
34+
mock_config.smtp_user = "user@example.com"
35+
mock_config.smtp_password = None
36+
mock_config.smtp_tls = False
37+
mock_config.get_db_correos_connection_string.return_value = "c_conn"
38+
mock_config.get_db_connection_string.return_value = "t_conn"
39+
40+
# Two pending in correos, one in tareas
41+
correos_rows = [
42+
{
43+
"IDCorreo": 1,
44+
"Aplicacion": "App",
45+
"Destinatarios": "a@example.com",
46+
"Asunto": "A1",
47+
"Cuerpo": "Body1",
48+
},
49+
{
50+
"IDCorreo": 2,
51+
"Aplicacion": "App",
52+
"Destinatarios": "b@example.com",
53+
"Asunto": "A2",
54+
"Cuerpo": "Body2",
55+
},
56+
]
57+
tareas_rows = [
58+
{
59+
"IDCorreo": 10,
60+
"Aplicacion": "Task",
61+
"Destinatarios": "c@example.com",
62+
"Asunto": "T1",
63+
"Cuerpo": "TB",
64+
},
65+
]
66+
67+
correos_pool = DummyPool(correos_rows)
68+
tareas_pool = DummyPool(tareas_rows)
69+
mock_get_correos_pool.return_value = correos_pool
70+
mock_get_tareas_pool.return_value = tareas_pool
71+
72+
smtp_inst = MagicMock()
73+
mock_smtp.return_value.__enter__.return_value = smtp_inst
74+
75+
task = EmailServicesTask()
76+
ok = task.execute_specific_logic()
77+
78+
assert ok is True
79+
# We expect three sendmail calls total
80+
assert smtp_inst.sendmail.call_count == 3
81+
# Each pool should have update calls matching its rows
82+
assert len(correos_pool.updates) == 2
83+
assert len(tareas_pool.updates) == 1
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import pytest
2+
3+
from common.config import Config
4+
from common.config import config as global_config
5+
6+
7+
@pytest.mark.parametrize(
8+
"db_type,alias_method,kwargs",
9+
[
10+
("agedys", "get_db_agedys_connection_string", {}),
11+
("brass", "get_db_brass_connection_string", {}),
12+
("tareas", "get_db_tareas_connection_string", {}),
13+
("correos", "get_db_correos_connection_string", {"with_password": True}),
14+
("correos", "get_db_correos_connection_string", {"with_password": False}),
15+
("riesgos", "get_db_riesgos_connection_string", {}),
16+
("expedientes", "get_db_expedientes_connection_string", {}),
17+
("no_conformidades", "get_db_no_conformidades_connection_string", {}),
18+
],
19+
)
20+
def test_legacy_alias_methods_match_generic(db_type, alias_method, kwargs):
21+
"""Cada método legacy debe producir exactamente la misma cadena que el método genérico."""
22+
# Usar una nueva instancia para evitar que otros tests hayan mutado rutas
23+
cfg = Config()
24+
25+
# Resultado alias
26+
alias_func = getattr(cfg, alias_method)
27+
alias_value = alias_func(**kwargs) if kwargs else alias_func()
28+
29+
# Resultado genérico (con posible control de password)
30+
with_password = kwargs.get("with_password", True)
31+
generic_value = cfg.get_db_connection_string(db_type, with_password=with_password)
32+
33+
assert alias_value == generic_value, (
34+
f"Alias {alias_method} difiere del genérico para {db_type}.\n"
35+
f"Alias: {alias_value}\nGenérico: {generic_value}"
36+
)
37+
38+
39+
def test_global_config_instance_alias_equivalence():
40+
"""Verifica también contra la instancia global por si se usa en producción directamente."""
41+
# Asegurar que la instancia global se comporta igual al crear otra nueva
42+
fresh = Config()
43+
assert (
44+
global_config.get_db_brass_connection_string()
45+
== fresh.get_db_brass_connection_string()
46+
)
47+
assert global_config.get_db_connection_string(
48+
"brass"
49+
) == fresh.get_db_connection_string("brass")
50+
# Consistencia cruzada alias vs genérico en la instancia global
51+
assert (
52+
global_config.get_db_brass_connection_string()
53+
== global_config.get_db_connection_string("brass")
54+
)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from unittest.mock import MagicMock
2+
3+
from common.utils import execute_task_with_standard_boilerplate
4+
5+
6+
class DummyTask:
7+
"""Mock sencillo de una Task con interfaz mínima utilizada por el helper."""
8+
9+
def __init__(self):
10+
self.debe_ejecutarse = MagicMock(return_value=True)
11+
self.execute_specific_logic = MagicMock(return_value=True)
12+
self.marcar_como_completada = MagicMock()
13+
# Soporta context manager
14+
15+
def __enter__(self):
16+
return self
17+
18+
def __exit__(self, exc_type, exc, tb):
19+
return False
20+
21+
22+
def test_no_force_not_due_skips_logic(tmp_path, monkeypatch):
23+
task = DummyTask()
24+
task.debe_ejecutarse.return_value = False
25+
26+
exit_code = execute_task_with_standard_boilerplate(
27+
"Dummy", task_obj=task, log_file=tmp_path / "dummy.log"
28+
)
29+
30+
task.execute_specific_logic.assert_not_called()
31+
task.marcar_como_completada.assert_not_called()
32+
assert exit_code == 0
33+
34+
35+
def test_no_force_due_executes_and_marks(tmp_path):
36+
task = DummyTask()
37+
task.debe_ejecutarse.return_value = True
38+
39+
exit_code = execute_task_with_standard_boilerplate(
40+
"Dummy", task_obj=task, log_file=tmp_path / "dummy.log"
41+
)
42+
43+
task.execute_specific_logic.assert_called_once()
44+
task.marcar_como_completada.assert_called_once()
45+
assert exit_code == 0
46+
47+
48+
def test_force_executes_without_mark(tmp_path):
49+
task = DummyTask()
50+
51+
exit_code = execute_task_with_standard_boilerplate(
52+
"Dummy", task_obj=task, force=True, log_file=tmp_path / "dummy.log"
53+
)
54+
55+
task.execute_specific_logic.assert_called_once()
56+
task.marcar_como_completada.assert_not_called()
57+
assert exit_code == 0
58+
59+
60+
def test_execute_specific_logic_exception_returns_error_code(tmp_path):
61+
task = DummyTask()
62+
task.execute_specific_logic.side_effect = RuntimeError("boom")
63+
64+
exit_code = execute_task_with_standard_boilerplate(
65+
"Dummy", task_obj=task, log_file=tmp_path / "dummy.log"
66+
)
67+
68+
task.execute_specific_logic.assert_called_once()
69+
# Como falla, no se debería marcar
70+
task.marcar_como_completada.assert_not_called()
71+
assert exit_code == 1

0 commit comments

Comments
 (0)