feature/generador-instaladores-linux-windows
This commit is contained in:
170
tests/test_forward_false_failure.py
Normal file
170
tests/test_forward_false_failure.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Regresión del "falso fallo" en el reenvío por SFTP.
|
||||
|
||||
Escenario reportado: el SFTP SÍ entrega el archivo, pero un error POSTERIOR a la entrega
|
||||
(p.ej. al mover el ZIP a Procesados) degradaba el job a FAILED y lo reportaba como 'failed'
|
||||
al panel, aunque el respaldo ya había llegado al destino. La corrección hace que la entrega
|
||||
exitosa sea el punto de no retorno: el job queda COMPLETED/forwarded y el error posterior
|
||||
solo se registra, sin caer a Fallados ni reportar 'failed'.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from app.constants import JobStatus
|
||||
from app.engine.restore_worker import RestoreWorker
|
||||
|
||||
|
||||
FORWARD_ROUTE = {
|
||||
"action": "forward",
|
||||
"db_name": "DB1",
|
||||
"node_key": "NODO",
|
||||
"target": {
|
||||
"id": 7,
|
||||
"name": "Omega",
|
||||
"ssh_host": "h",
|
||||
"ssh_username": "u",
|
||||
"ssh_password": "p",
|
||||
"input_folder": "D:\\In",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def base_config(tmp_path):
|
||||
return {
|
||||
"paths": {
|
||||
"input_folder": str(tmp_path / "in"),
|
||||
"processed_folder": str(tmp_path / "processed"),
|
||||
"failed_folder": str(tmp_path / "failed"),
|
||||
"extract_folder": str(tmp_path / "extract"),
|
||||
"data_sql_folder": str(tmp_path / "data"),
|
||||
"seven_zip_exe": "C:\\Program Files\\7-Zip\\7z.exe",
|
||||
},
|
||||
"sql": {"server": "localhost", "use_windows_auth": True},
|
||||
"timeouts": {"extract_minutes": 30, "restore_minutes": 60},
|
||||
"panel": {
|
||||
"api_url": "http://panel:3000",
|
||||
"api_token": "tok",
|
||||
"instance_key": "Alfa",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class FakeJob:
|
||||
source_name = "NODO.ZIP"
|
||||
source_path = "C:\\in\\NODO.ZIP"
|
||||
db_name = "DB1"
|
||||
node_name = "NODO"
|
||||
|
||||
|
||||
def _wire_common(monkeypatch, statuses, reported):
|
||||
"""Mockea las dependencias del worker comunes a los dos escenarios."""
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.JobRepository.get", lambda job_id: FakeJob()
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.JobRepository.update_node_and_db",
|
||||
lambda *a, **k: None,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.JobRepository.update_timing", lambda *a, **k: None
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.JobRepository.update_status",
|
||||
lambda job_id, status, **k: statuses.append(status),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.JobRepository.delete", lambda *a, **k: None
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.JobStepRepository.create", lambda *a, **k: 1
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.JobStepRepository.complete", lambda *a, **k: None
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.EventRepository.create", lambda *a, **k: None
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.panel_client.resolve_route",
|
||||
lambda *a, **k: FORWARD_ROUTE,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.panel_client.report_job_result",
|
||||
lambda **k: reported.append(k.get("status")),
|
||||
)
|
||||
|
||||
|
||||
def test_error_post_entrega_no_degrada_a_fallido(monkeypatch, base_config):
|
||||
"""Entrega OK + error al mover a Procesados => job forwarded, nunca failed."""
|
||||
statuses: list[str] = []
|
||||
reported: list[str] = []
|
||||
_wire_common(monkeypatch, statuses, reported)
|
||||
|
||||
# El SFTP entrega con éxito.
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.sftp_copy.upload_zip_parts",
|
||||
lambda *a, **k: ["D:/In/NODO.ZIP"],
|
||||
)
|
||||
|
||||
moved_to_failed = {"called": False}
|
||||
monkeypatch.setattr(
|
||||
RestoreWorker,
|
||||
"_move_zip_to_failed",
|
||||
lambda self, job: moved_to_failed.__setitem__("called", True),
|
||||
)
|
||||
|
||||
# Error POSTERIOR a la entrega: mover a Procesados falla.
|
||||
def boom(self, job):
|
||||
raise OSError("disco lleno al mover a Procesados")
|
||||
|
||||
monkeypatch.setattr(RestoreWorker, "_move_zip_to_processed", boom)
|
||||
|
||||
worker = RestoreWorker("job-1", base_config)
|
||||
worker.run()
|
||||
|
||||
assert JobStatus.COMPLETED in statuses
|
||||
assert JobStatus.FAILED not in statuses
|
||||
assert reported == ["forwarded"]
|
||||
assert moved_to_failed["called"] is False
|
||||
|
||||
|
||||
def test_fallo_real_de_sftp_va_a_fallados_y_limpia_parcial(monkeypatch, base_config):
|
||||
"""Fallo genuino de entrega => FAILED, ZIP a Fallados, y limpieza de partes subidas."""
|
||||
from app.transfer.sftp_copy import SFTPCopyError
|
||||
|
||||
statuses: list[str] = []
|
||||
reported: list[str] = []
|
||||
_wire_common(monkeypatch, statuses, reported)
|
||||
|
||||
# El SFTP falla en la parte 2, adjuntando lo ya subido (envío parcial).
|
||||
err = SFTPCopyError("timeout en la parte 2")
|
||||
err.uploaded = ["D:/In/NODO.ZIP.001"]
|
||||
|
||||
def failing_upload(*a, **k):
|
||||
raise err
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.sftp_copy.upload_zip_parts", failing_upload
|
||||
)
|
||||
|
||||
cleaned: list[str] = []
|
||||
monkeypatch.setattr(
|
||||
"app.engine.restore_worker.sftp_copy.cleanup_remote",
|
||||
lambda target, path: cleaned.append(path),
|
||||
)
|
||||
|
||||
moved_to_failed = {"called": False}
|
||||
monkeypatch.setattr(
|
||||
RestoreWorker,
|
||||
"_move_zip_to_failed",
|
||||
lambda self, job: moved_to_failed.__setitem__("called", True),
|
||||
)
|
||||
|
||||
worker = RestoreWorker("job-1", base_config)
|
||||
worker.run()
|
||||
|
||||
assert JobStatus.FAILED in statuses
|
||||
assert JobStatus.COMPLETED not in statuses
|
||||
assert reported == ["failed"]
|
||||
assert moved_to_failed["called"] is True
|
||||
assert cleaned == ["D:/In/NODO.ZIP.001"]
|
||||
Reference in New Issue
Block a user