SIENTIAPDE-1163

refactor: update notification handling in activities to include metadata in notifications, enhancing context for error reporting and success messages; update requirements to use version 1.3.0 of the sientia-dataops-library
This commit is contained in:
vitor-aignosi
2025-07-16 13:14:16 -03:00
parent 225964f938
commit b67d23da1f
12 changed files with 449 additions and 178 deletions

View File

@@ -8,13 +8,26 @@ from orchestrator.utils.orchestrator_functions import build_tag_config
@fixture
def formatters():
return Formatters(
formatters = Formatters(
scouter_namespace="scouter",
laborious_namespace="laborious",
logger=MagicMock(),
notification_handler=MagicMock()
)
formatters.send_notification = MagicMock()
return formatters
metadata = {
"metadata": {
"schedule_name": "test_schedule_name",
"workflow_name": "test_workflow_name",
"model_name": "test_model_name",
"model_id": "test_model_id"
}
}
@mark.asyncio
@patch("orchestrator.activities.formatters.scouter",
@@ -374,23 +387,33 @@ async def test_create_slot_config(formatters):
def test_send_success_report(formatters):
formatters.send_success_report("test_message", "test_notification_id")
formatters.notification_handler.build_and_send_notification.assert_called_once_with(
"test_notification_id",
"test_message",
"report_orchestration",
NotificationLevel.INFO
formatters.send_success_report(
metadata=metadata,
message="test_message",
notification_id="test_notification_id"
)
formatters.send_notification.assert_called_once_with(
metadata=metadata,
notification_id="test_notification_id",
message="test_message",
block="report_orchestration",
level=NotificationLevel.INFO
)
def test_send_error_report(formatters):
formatters.send_error_report(
"test_message", "test_notification_id", {"test": "test"})
formatters.notification_handler.build_and_send_notification.assert_called_once_with(
"test_notification_id",
"test_message",
"report_orchestration",
NotificationLevel.ERROR,
metadata=metadata,
message="test_message",
notification_id="test_notification_id",
attachment={"test": "test"}
)
formatters.send_notification.assert_called_once_with(
metadata=metadata,
notification_id="test_notification_id",
message="test_message",
block="report_orchestration",
level=NotificationLevel.ERROR,
attachment_content=json.dumps(
{"test": "test"}, indent=4, sort_keys=True)
)
@@ -446,6 +469,7 @@ async def test_report_schedule_orchestration(formatters):
formatters.send_error_report = MagicMock()
input_data = {
**metadata,
"created_schedules": [
{
"namespace": "test_namespace",
@@ -496,33 +520,39 @@ async def test_report_schedule_orchestration(formatters):
])
formatters.send_success_report.assert_has_calls([
call(
"Created schedules: \n test_namespace/test_schedule_name_to_create",
"REPORT_ORCHESTRATION_CREATED_SCHEDULES"
metadata=metadata['metadata'],
message="Created schedules: \n test_namespace/test_schedule_name_to_create",
notification_id="REPORT_ORCHESTRATION_CREATED_SCHEDULES"
),
call(
"Updated schedules: \n test_namespace/test_schedule_name_to_update",
"REPORT_ORCHESTRATION_UPDATED_SCHEDULES"
metadata=metadata['metadata'],
message="Updated schedules: \n test_namespace/test_schedule_name_to_update",
notification_id="REPORT_ORCHESTRATION_UPDATED_SCHEDULES"
),
call(
"Deleted schedules: \n test_namespace/test_schedule_name_to_delete",
"REPORT_ORCHESTRATION_DELETED_SCHEDULES"
metadata=metadata['metadata'],
message="Deleted schedules: \n test_namespace/test_schedule_name_to_delete",
notification_id="REPORT_ORCHESTRATION_DELETED_SCHEDULES"
)
])
formatters.send_error_report.assert_has_calls([
call(
"Failed to create schedules: \n test_namespace/test_schedule_name_to_create_error: test_error",
"REPORT_ORCHESTRATION_CREATED_SCHEDULES",
input_data['created_schedules']
metadata=metadata['metadata'],
message="Failed to create schedules: \n test_namespace/test_schedule_name_to_create_error: test_error",
notification_id="REPORT_ORCHESTRATION_CREATED_SCHEDULES",
attachment=input_data['created_schedules']
),
call(
"Failed to update schedules: \n test_namespace/test_schedule_name_to_update_error: test_error",
"REPORT_ORCHESTRATION_UPDATED_SCHEDULES",
input_data['updated_schedules']
metadata=metadata['metadata'],
message="Failed to update schedules: \n test_namespace/test_schedule_name_to_update_error: test_error",
notification_id="REPORT_ORCHESTRATION_UPDATED_SCHEDULES",
attachment=input_data['updated_schedules']
),
call(
"Failed to delete schedules: \n test_namespace/test_schedule_name_to_delete_error: test_error",
"REPORT_ORCHESTRATION_DELETED_SCHEDULES",
input_data['deleted_schedules']
metadata=metadata['metadata'],
message="Failed to delete schedules: \n test_namespace/test_schedule_name_to_delete_error: test_error",
notification_id="REPORT_ORCHESTRATION_DELETED_SCHEDULES",
attachment=input_data['deleted_schedules']
)
])
@@ -536,6 +566,7 @@ async def test_report_slot_orchestration(formatters):
formatters.send_error_report = MagicMock()
input_data = {
**metadata,
"inserted_slots": {
"test_slot_name_to_create": {
"success": True
@@ -564,23 +595,27 @@ async def test_report_slot_orchestration(formatters):
])
formatters.send_success_report.assert_has_calls([
call(
"Inserted slots: \n test_slot_name_to_create",
"REPORT_ORCHESTRATION_INSERTED_SLOTS"
metadata=metadata['metadata'],
message="Inserted slots: \n test_slot_name_to_create",
notification_id="REPORT_ORCHESTRATION_INSERTED_SLOTS"
),
call(
"Deleted slots: \n test_slot_name_to_delete",
"REPORT_ORCHESTRATION_DELETED_SLOTS"
metadata=metadata['metadata'],
message="Deleted slots: \n test_slot_name_to_delete",
notification_id="REPORT_ORCHESTRATION_DELETED_SLOTS"
)
])
formatters.send_error_report.assert_has_calls([
call(
"Failed to insert slots: \n test_slot_name_to_create_error",
"REPORT_ORCHESTRATION_INSERTED_SLOTS",
input_data['inserted_slots']
metadata=metadata['metadata'],
message="Failed to insert slots: \n test_slot_name_to_create_error",
notification_id="REPORT_ORCHESTRATION_INSERTED_SLOTS",
attachment=input_data['inserted_slots']
),
call(
"Failed to delete slots: \n test_slot_name_to_delete_error",
"REPORT_ORCHESTRATION_DELETED_SLOTS",
input_data['deleted_slots']
metadata=metadata['metadata'],
message="Failed to delete slots: \n test_slot_name_to_delete_error",
notification_id="REPORT_ORCHESTRATION_DELETED_SLOTS",
attachment=input_data['deleted_slots']
)
])

View File

@@ -1,3 +1,4 @@
from curses import meta
from unittest.mock import MagicMock, patch, ANY
from pytest import fixture, mark
from orchestrator.activities.mongo_db import clear_mongo_id
@@ -50,7 +51,7 @@ def test_clear_mongo_id():
@fixture
@patch("orchestrator.activities.mongo_db.MongoClient")
def mongo_db(mongo_mock):
return (
mongo = (
MongoDB(
connection_string="mongodb://localhost:27017",
database_name="test_db",
@@ -58,6 +59,8 @@ def mongo_db(mongo_mock):
notification_handler=MagicMock()
)
)
mongo.send_notification = MagicMock()
return mongo
@patch("orchestrator.activities.mongo_db.MongoClient")
@@ -117,6 +120,15 @@ async def test_find_documents_in_mongodb_success(mongo_db):
{"name": {"$exists": True}}, {"_id": 0}
)
metadata = {
"metadata": {
"schedule_name": "test_schedule_name",
"workflow_name": "test_workflow_name",
"model_name": "test_model_name",
"model_id": "test_model_id"
}
}
@mark.asyncio
async def test_find_documents_in_mongodb_failure(mongo_db):
@@ -130,11 +142,13 @@ async def test_find_documents_in_mongodb_failure(mongo_db):
try:
await mongo_db.find_documents_in_mongodb(
{
"query": input_data
"query": input_data,
**metadata
})
except Exception as e:
assert str(e) == "Error"
mongo_db.notification_handler.build_and_send_notification.assert_called_once_with(
mongo_db.send_notification.assert_called_once_with(
metadata=metadata['metadata'],
notification_id="MONGODB_QUERY_ERROR",
message="Failed to execute MongoDB query: Error",
level=NotificationLevel.ERROR,
@@ -202,15 +216,17 @@ async def test_aggregate_documents_in_mongodb_failure(mongo_db):
try:
await mongo_db.aggregate_documents_in_mongodb(
{
"query": input_data
"query": input_data,
**metadata
})
except Exception as e:
assert str(e) == "Error"
mongo_db.notification_handler.build_and_send_notification.assert_called_once_with(
mongo_db.send_notification.assert_called_once_with(
metadata=metadata['metadata'],
notification_id="MONGODB_AGGREGATION_ERROR",
message="Failed to execute MongoDB aggregation: Error",
level=NotificationLevel.ERROR,
block="aggregate_documents_in_mongodb",
level=NotificationLevel.ERROR,
attachment_content=ANY
)

View File

@@ -2,6 +2,15 @@ from unittest.mock import MagicMock, patch, call
from pytest import mark, fixture
from orchestrator.activities.slot_manager import SlotManager
metadata = {
"metadata": {
"schedule_name": "test_schedule_name",
"workflow_name": "test_workflow_name",
"model_name": "test_model_name",
"model_id": "test_model_id"
}
}
@fixture
@patch("orchestrator.activities.slot_manager.Redis.__init__")
@@ -26,7 +35,7 @@ def slot_manager(_redis_mock):
@mark.asyncio
async def test_load_opc_slots_no_slot_keys(slot_manager):
slot_manager.redis_client.keys.return_value = []
assert await slot_manager.load_opc_slots() == {}
assert await slot_manager.load_opc_slots(metadata) == {}
@mark.asyncio
@@ -42,7 +51,7 @@ async def test_load_opc_slots(slot_manager):
]
)
response = await slot_manager.load_opc_slots()
response = await slot_manager.load_opc_slots(metadata)
assert response == {
"slot:opc_tags:1": "value1",
@@ -64,7 +73,7 @@ async def test_load_opc_slots_no_decode(slot_manager):
]
)
response = await slot_manager.load_opc_slots()
response = await slot_manager.load_opc_slots(metadata)
assert response == {
"slot:opc_tags:1": "value1",
@@ -78,7 +87,7 @@ async def test_load_active_ingestors(slot_manager):
slot_manager.redis_client.keys.return_value = [
b"heartbeat:ingestor:1", b"heartbeat:ingestor:2", "heartbeat:ingestor:3"]
response = await slot_manager.load_active_ingestors()
response = await slot_manager.load_active_ingestors(metadata)
assert response == ["heartbeat:ingestor:1",
"heartbeat:ingestor:2", "heartbeat:ingestor:3"]

View File

@@ -7,6 +7,15 @@ import pytest_asyncio
from orchestrator.activities.temporal_manager import TemporalManager
from orchestrator.utils.converters import parse_frequency
metadata = {
"metadata": {
"schedule_name": "test_schedule_name",
"workflow_name": "test_workflow_name",
"model_name": "test_model_name",
"model_id": "test_model_id"
}
}
@fixture
@patch("orchestrator.activities.temporal_manager.Client.connect")
@@ -113,7 +122,7 @@ async def test_load_schedule(_mock_message_to_dict, temporal_manager):
temporal_manager.temporal_clients['laborious'].get_schedule_handle.return_value.describe \
.return_value.schedule.spec = describe_mock
response = await temporal_manager.load_schedule()
response = await temporal_manager.load_schedule(metadata)
temporal_manager.temporal_clients['scouter'].list_schedules.assert_awaited_once(
)

View File

@@ -9,6 +9,16 @@ def orchestrator():
return Orchestrator()
metadata = {
'metadata': {
'schedule_name': 'test-schedule-name',
'workflow_name': 'orchestrator',
'model_name': '-',
'model_id': '-',
}
}
@mark.asyncio
@patch("orchestrator.workflows.orchestrator.workflow", new_callable=AsyncMock)
async def test_run(workflow_mock, orchestrator):
@@ -24,7 +34,8 @@ async def test_run(workflow_mock, orchestrator):
call(
Activities.aggregate_documents_in_mongodb,
{
"query": input_data["pipelines_query"]
"query": input_data["pipelines_query"],
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -35,7 +46,8 @@ async def test_run(workflow_mock, orchestrator):
call(
Activities.find_documents_in_mongodb,
{
"query": input_data["opc_servers_query"]
"query": input_data["opc_servers_query"],
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -48,7 +60,8 @@ async def test_run(workflow_mock, orchestrator):
{
"query": {
"collection": "orchestrated_schedules"
}
},
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -58,6 +71,9 @@ async def test_run(workflow_mock, orchestrator):
workflow_mock.start_local_activity_method.assert_has_calls([
call(
Activities.load_opc_slots,
{
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
)
@@ -66,6 +82,9 @@ async def test_run(workflow_mock, orchestrator):
workflow_mock.start_local_activity_method.assert_has_calls([
call(
Activities.load_active_ingestors,
{
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
)
@@ -75,7 +94,8 @@ async def test_run(workflow_mock, orchestrator):
call(
Activities.format_schedule_config,
{
'schedule_config': workflow_mock.start_local_activity_method.return_value
'schedule_config': workflow_mock.start_local_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -86,7 +106,8 @@ async def test_run(workflow_mock, orchestrator):
call(
Activities.process_schedules,
{
'pipelines': workflow_mock.start_local_activity_method.return_value
'pipelines': workflow_mock.start_local_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -100,6 +121,7 @@ async def test_run(workflow_mock, orchestrator):
'opc_servers': workflow_mock.start_local_activity_method.return_value,
'active_ingestors': workflow_mock.start_local_activity_method.return_value,
'pipelines': workflow_mock.start_local_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -111,7 +133,8 @@ async def test_run(workflow_mock, orchestrator):
Activities.create_schedule_config,
{
'current_schedule_config': workflow_mock.start_local_activity_method.return_value,
'schedule_config': workflow_mock.start_local_activity_method.return_value
'schedule_config': workflow_mock.start_local_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -123,7 +146,8 @@ async def test_run(workflow_mock, orchestrator):
Activities.create_slot_config,
{
'current_slot_config': workflow_mock.start_local_activity_method.return_value,
'slot_config': workflow_mock.start_local_activity_method.return_value
'slot_config': workflow_mock.start_local_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -134,7 +158,8 @@ async def test_run(workflow_mock, orchestrator):
call(
Activities.normalize_schedules,
{
'orchestrated_schedules': workflow_mock.start_local_activity_method.return_value
'orchestrated_schedules': workflow_mock.start_local_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -146,7 +171,8 @@ async def test_run(workflow_mock, orchestrator):
Activities.delete_slots,
{
'to_delete':
workflow_mock.start_local_activity_method.return_value['to_delete']
workflow_mock.start_local_activity_method.return_value['to_delete'],
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -158,7 +184,8 @@ async def test_run(workflow_mock, orchestrator):
Activities.update_slots,
{
'to_insert':
workflow_mock.start_local_activity_method.return_value['to_insert']
workflow_mock.start_local_activity_method.return_value['to_insert'],
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -170,7 +197,8 @@ async def test_run(workflow_mock, orchestrator):
Activities.delete_schedules,
{
'schedules':
workflow_mock.start_local_activity_method.return_value['to_delete']
workflow_mock.start_local_activity_method.return_value['to_delete'],
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -182,7 +210,8 @@ async def test_run(workflow_mock, orchestrator):
Activities.create_schedules,
{
'schedules':
workflow_mock.start_local_activity_method.return_value['to_create']
workflow_mock.start_local_activity_method.return_value['to_create'],
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -194,7 +223,8 @@ async def test_run(workflow_mock, orchestrator):
Activities.update_schedules,
{
'schedules':
workflow_mock.start_local_activity_method.return_value['to_update']
workflow_mock.start_local_activity_method.return_value['to_update'],
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -207,7 +237,8 @@ async def test_run(workflow_mock, orchestrator):
{
'created_schedules': workflow_mock.start_activity_method.return_value,
'updated_schedules': workflow_mock.start_activity_method.return_value,
'deleted_schedules': workflow_mock.start_activity_method.return_value
'deleted_schedules': workflow_mock.start_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -219,7 +250,8 @@ async def test_run(workflow_mock, orchestrator):
Activities.report_slot_orchestration,
{
'inserted_slots': workflow_mock.start_activity_method.return_value,
'deleted_slots': workflow_mock.start_activity_method.return_value
'deleted_slots': workflow_mock.start_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -230,7 +262,8 @@ async def test_run(workflow_mock, orchestrator):
call(
Activities.update_pipelines_timestamps,
{
'updated_pipelines': workflow_mock.start_activity_method.return_value
'updated_pipelines': workflow_mock.start_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -241,7 +274,8 @@ async def test_run(workflow_mock, orchestrator):
call(
Activities.delete_pipelines_timestamps,
{
'deleted_pipelines': workflow_mock.start_activity_method.return_value
'deleted_pipelines': workflow_mock.start_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY
@@ -252,7 +286,8 @@ async def test_run(workflow_mock, orchestrator):
call(
Activities.create_pipelines_timestamps,
{
'created_pipelines': workflow_mock.start_activity_method.return_value
'created_pipelines': workflow_mock.start_activity_method.return_value,
**metadata
},
retry_policy=ANY,
start_to_close_timeout=ANY