QTZPOC-20

QTZPOC-20: Enhance system robustness and test coverage across bundle processing, activity error handling, and worker operations.

*   Strengthen bundle reader validation with new tests for archive integrity, security vulnerabilities (e.g., path traversal, duplicate entries, special file types), and I/O error resilience.
*   Improve error handling and status propagation in PI Web API, MLflow, and OPC activities, including partial write scenarios and OPC session management.
*   Add comprehensive tests for worker startup, configuration, graceful shutdown, and Prometheus metrics server initialization.
*   Introduce a new utility for intelligent DataFrame debugging, summarizing large dataframes for concise logging, and refine exception chain summarization.
*   Update code coverage configuration to exclude @overload decorator lines.
This commit is contained in:
2026-09-08 09:42:23 -03:00
parent 61db575cdc
commit e4fc12e529
15 changed files with 891 additions and 1 deletions

View File

@@ -0,0 +1,245 @@
"""``main()`` and ``start_prometheus_server()``: the orchestration paths ``test_runtime_task_queues``
doesn't reach.
Everything I/O-bound (Temporal client, Prometheus HTTP server, Mongo/Postgres/MinIO configs) is
mocked; only the control flow inside ``worker.py`` is under test.
"""
from unittest.mock import ANY, AsyncMock, MagicMock, patch
from pytest import mark, raises
from laborious.worker.worker import main, start_prometheus_server
@patch('laborious.worker.worker.metrics.APP_UP')
@patch('laborious.worker.worker.get_logger')
def test_main_exits_when_runtime_is_unset(mock_get_logger, mock_app_up, monkeypatch):
monkeypatch.delenv('RUNTIME', raising=False)
logger = MagicMock()
mock_get_logger.return_value = logger
with raises(SystemExit) as exc:
import asyncio
asyncio.run(main())
assert exc.value.code == 1
logger.custom_critical.assert_called_once()
mock_app_up.labels.return_value.set.assert_called_once_with(0)
@patch('laborious.worker.worker.prepare_worker')
@patch('laborious.worker.worker.client.Client.connect', new_callable=AsyncMock)
@patch('laborious.worker.worker.TelemetryConfig')
@patch('laborious.worker.worker.PrometheusConfig')
@patch('laborious.worker.worker.Runtime')
@patch('laborious.worker.worker.Activities')
@patch('laborious.worker.worker.NotificationHandler')
@patch('laborious.worker.worker.build_import_status_config')
@patch('laborious.worker.worker.build_import_config')
@patch('laborious.worker.worker.build_api_config')
@patch('laborious.worker.worker.build_opc_config')
@patch('laborious.worker.worker.build_minio_config')
@patch('laborious.worker.worker.build_mlflow_config')
@patch('laborious.worker.worker.build_postgres_config')
@patch('laborious.worker.worker.build_mongodb_config')
@patch('laborious.worker.worker.start_prometheus_server')
@patch('laborious.worker.worker.get_logger')
class TestMainHappyPath:
"""One class so every test shares the same (long) patch stack."""
def _run(self, mock_prepare_worker, mock_connect):
worker = MagicMock()
worker.run = AsyncMock(return_value=None)
mock_prepare_worker.return_value = worker
mock_connect.return_value = MagicMock()
import asyncio
with raises(SystemExit) as exc:
asyncio.run(main())
return exc.value.code
def test_runs_four_workers_without_the_import_flag( # noqa: PLR0913
self,
mock_get_logger,
_mock_start_prometheus,
_mock_mongo_cfg,
_mock_pg_cfg,
_mock_mlflow_cfg,
_mock_minio_cfg,
_mock_opc_cfg,
_mock_api_cfg,
_mock_import_cfg,
_mock_import_status_cfg,
mock_notification_handler,
mock_activities,
_mock_runtime,
_mock_prometheus_config,
_mock_telemetry_config,
mock_connect,
mock_prepare_worker,
monkeypatch,
):
monkeypatch.setenv('RUNTIME', 'test-runtime')
monkeypatch.delenv('IMPORT_MODEL_ENABLED', raising=False)
mock_get_logger.return_value = MagicMock()
mock_activities.return_value.init_opc = AsyncMock()
mock_activities.return_value.shutdown = AsyncMock()
exit_code = self._run(mock_prepare_worker, mock_connect)
assert exit_code == 0
assert mock_prepare_worker.call_count == 4
mock_activities.return_value.init_opc.assert_awaited_once()
mock_activities.return_value.shutdown.assert_awaited_once()
mock_notification_handler.return_value.shutdown.assert_called_once()
def test_runs_five_workers_with_the_import_flag( # noqa: PLR0913
self,
mock_get_logger,
_mock_start_prometheus,
_mock_mongo_cfg,
_mock_pg_cfg,
_mock_mlflow_cfg,
_mock_minio_cfg,
_mock_opc_cfg,
_mock_api_cfg,
_mock_import_cfg,
_mock_import_status_cfg,
_mock_notification_handler,
mock_activities,
_mock_runtime,
_mock_prometheus_config,
_mock_telemetry_config,
mock_connect,
mock_prepare_worker,
monkeypatch,
):
monkeypatch.setenv('RUNTIME', 'test-runtime')
monkeypatch.setenv('IMPORT_MODEL_ENABLED', 'true')
mock_get_logger.return_value = MagicMock()
mock_activities.return_value.init_opc = AsyncMock()
mock_activities.return_value.shutdown = AsyncMock()
exit_code = self._run(mock_prepare_worker, mock_connect)
assert exit_code == 0
assert mock_prepare_worker.call_count == 5
def test_a_falsy_notification_handler_or_activities_skips_shutdown( # noqa: PLR0913
self,
mock_get_logger,
_mock_start_prometheus,
_mock_mongo_cfg,
_mock_pg_cfg,
_mock_mlflow_cfg,
_mock_minio_cfg,
_mock_opc_cfg,
_mock_api_cfg,
_mock_import_cfg,
_mock_import_status_cfg,
mock_notification_handler,
mock_activities,
_mock_runtime,
_mock_prometheus_config,
_mock_telemetry_config,
mock_connect,
mock_prepare_worker,
monkeypatch,
):
"""Defensive `if notification_handler:` / `if activities:` guards around shutdown — real
construction always yields truthy instances, but the guard itself still needs a test."""
monkeypatch.setenv('RUNTIME', 'test-runtime')
monkeypatch.delenv('IMPORT_MODEL_ENABLED', raising=False)
mock_get_logger.return_value = MagicMock()
mock_activities.return_value.init_opc = AsyncMock()
mock_activities.return_value.shutdown = AsyncMock()
mock_activities.return_value.__bool__.return_value = False
mock_notification_handler.return_value.__bool__.return_value = False
exit_code = self._run(mock_prepare_worker, mock_connect)
assert exit_code == 0
mock_activities.return_value.shutdown.assert_not_called()
mock_notification_handler.return_value.shutdown.assert_not_called()
def test_a_worker_failure_exits_non_zero_and_still_cleans_up( # noqa: PLR0913
self,
mock_get_logger,
_mock_start_prometheus,
_mock_mongo_cfg,
_mock_pg_cfg,
_mock_mlflow_cfg,
_mock_minio_cfg,
_mock_opc_cfg,
_mock_api_cfg,
_mock_import_cfg,
_mock_import_status_cfg,
mock_notification_handler,
mock_activities,
_mock_runtime,
_mock_prometheus_config,
_mock_telemetry_config,
mock_connect,
mock_prepare_worker,
monkeypatch,
):
monkeypatch.setenv('RUNTIME', 'test-runtime')
monkeypatch.delenv('IMPORT_MODEL_ENABLED', raising=False)
logger = MagicMock()
mock_get_logger.return_value = logger
mock_activities.return_value.init_opc = AsyncMock()
mock_activities.return_value.shutdown = AsyncMock()
mock_connect.return_value = MagicMock()
worker = MagicMock()
worker.run = AsyncMock(side_effect=RuntimeError('boom'))
mock_prepare_worker.return_value = worker
import asyncio
with raises(SystemExit) as exc:
asyncio.run(main())
assert exc.value.code == 1
logger.custom_error.assert_called_once_with(ANY, ANY)
mock_activities.return_value.shutdown.assert_awaited_once()
mock_notification_handler.return_value.shutdown.assert_called_once()
@patch('laborious.worker.worker.metrics.APP_UP')
@patch('laborious.worker.worker.start_http_server')
def test_start_prometheus_server_marks_the_app_up(mock_start_http_server, mock_app_up):
start_prometheus_server()
mock_start_http_server.assert_called_once()
mock_app_up.labels.return_value.set.assert_called_once_with(1)
@patch('laborious.worker.worker.os._exit')
@patch('laborious.worker.worker.metrics.APP_UP')
@patch('laborious.worker.worker.start_http_server')
def test_start_prometheus_server_exits_the_process_on_failure(
mock_start_http_server, mock_app_up, mock_os_exit
):
mock_start_http_server.side_effect = OSError('port in use')
start_prometheus_server()
mock_os_exit.assert_called_once_with(1)
mock_app_up.labels.return_value.set.assert_not_called()
@mark.parametrize('port_env', ['9999'])
@patch('laborious.worker.worker.metrics.APP_UP')
@patch('laborious.worker.worker.start_http_server')
def test_start_prometheus_server_reads_the_configured_port(
mock_start_http_server, _mock_app_up, port_env, monkeypatch
):
monkeypatch.setenv('HTTP_METRICS_PORT', port_env)
start_prometheus_server()
mock_start_http_server.assert_called_once_with(int(port_env))