mirror of
https://github.com/hoshikawa2/compass_backoffice.git
synced 2026-07-09 13:54:20 +00:00
188 lines
7.5 KiB
Python
188 lines
7.5 KiB
Python
import pytest
|
|
from unittest.mock import patch, AsyncMock
|
|
from src.agent.nodes.imdb_enrichment_node import imdb_enrich_ticket, should_continue
|
|
from src.components.clients.exceptions.imdb_exceptions import (
|
|
ImdbClientError,
|
|
ImdbHttpError,
|
|
ImdbTimeoutError,
|
|
ImdbConnectionError,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def make_state(request_context=None, session_id="sess-123"):
|
|
"""Build a minimal AgentState for tests."""
|
|
return {
|
|
"messages": [],
|
|
"current_step": "start",
|
|
"iteration_count": 0,
|
|
"tool_results": None,
|
|
"final_response": None,
|
|
"session_id": session_id,
|
|
"metadata": {"request_context": request_context} if request_context else {},
|
|
"error": None,
|
|
}
|
|
|
|
|
|
def _valid_context(cpf_cnpj="12345678901", msisdn="5511999999999"):
|
|
return {
|
|
"customer": {
|
|
"cpfCnpj": cpf_cnpj,
|
|
"msisdn": msisdn,
|
|
}
|
|
}
|
|
|
|
|
|
MOCK_IMDB_RESPONSE = {
|
|
"plan": "TIM Black",
|
|
"statusType": "Active",
|
|
"statusDescription": "Acesso Full",
|
|
"socialSecNo": "06252533106",
|
|
}
|
|
|
|
IMDB_CLIENT_PATH = "src.agent.nodes.imdb_enrichment_node.ImdbClient"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.asyncio
|
|
class TestImdbEnrichmentNode:
|
|
|
|
# --- Happy path ---------------------------------------------------------
|
|
|
|
@patch(IMDB_CLIENT_PATH)
|
|
async def test_imdb_enrichment_node_success(self, mock_client_cls):
|
|
"""Node should store imdb_access_data in context and set step to 'imdb_enrichment_completed'."""
|
|
# Arrange
|
|
mock_client = AsyncMock()
|
|
mock_client.get_imdb_access_data_with_retry.return_value = AsyncMock(model_dump=lambda: MOCK_IMDB_RESPONSE)
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
state = make_state(request_context=_valid_context())
|
|
|
|
# Act
|
|
final_state = await imdb_enrich_ticket(state)
|
|
|
|
# Assert
|
|
assert final_state["current_step"] == "imdb_enrichment_completed"
|
|
assert final_state["error"] is None
|
|
|
|
imdb_data = final_state["metadata"]["request_context"]["imdb_access_data"]
|
|
assert imdb_data["plan"] == "TIM Black"
|
|
assert imdb_data["statusType"] == "Active"
|
|
assert imdb_data["cpf_cnpj"] == "12345678901"
|
|
assert imdb_data["socialSecNo"] == "06252533106"
|
|
mock_client.get_imdb_access_data_with_retry.assert_called_once()
|
|
|
|
# --- Validation errors --------------------------------------------------
|
|
|
|
async def test_imdb_enrichment_node_missing_context(self):
|
|
"""No request_context → ValidationError."""
|
|
state = make_state(request_context=None)
|
|
final_state = await imdb_enrich_ticket(state)
|
|
assert final_state["error"]["type"] == "ValidationError"
|
|
assert final_state["error"]["step"] == "imdb_enrichment_failed"
|
|
|
|
async def test_imdb_enrichment_node_missing_msisdn(self):
|
|
"""msisdn=None → Redirect (smart human handling)."""
|
|
state = make_state(request_context=_valid_context(msisdn=None))
|
|
final_state = await imdb_enrich_ticket(state)
|
|
assert final_state["current_step"] == "imdb_enrichment_completed"
|
|
assert final_state["metadata"]["request_context"]["smart_human_handling"] is True
|
|
|
|
# --- Client errors ------------------------------------------------------
|
|
|
|
@patch(IMDB_CLIENT_PATH)
|
|
async def test_imdb_enrichment_node_http_error(self, mock_client_cls):
|
|
"""ImdbHttpError → error.type == 'ImdbHttpError', step == 'imdb_enrichment_failed'."""
|
|
mock_client = AsyncMock()
|
|
mock_client.get_imdb_access_data_with_retry.side_effect = ImdbHttpError(404, "Not Found")
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
final_state = await imdb_enrich_ticket(make_state(request_context=_valid_context()))
|
|
|
|
assert final_state["error"]["type"] == "ImdbHttpError"
|
|
assert final_state["current_step"] == "imdb_enrichment_failed"
|
|
|
|
@patch(IMDB_CLIENT_PATH)
|
|
async def test_imdb_enrichment_node_timeout_error(self, mock_client_cls):
|
|
"""ImdbTimeoutError → error.type == 'ImdbTimeoutError'."""
|
|
mock_client = AsyncMock()
|
|
mock_client.get_imdb_access_data_with_retry.side_effect = ImdbTimeoutError("url", 5.0)
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
final_state = await imdb_enrich_ticket(make_state(request_context=_valid_context()))
|
|
|
|
assert final_state["error"]["type"] == "ImdbTimeoutError"
|
|
assert final_state["current_step"] == "imdb_enrichment_failed"
|
|
|
|
@patch(IMDB_CLIENT_PATH)
|
|
async def test_imdb_enrichment_node_connection_error(self, mock_client_cls):
|
|
"""ImdbConnectionError → error.type == 'ImdbConnectionError'."""
|
|
mock_client = AsyncMock()
|
|
mock_client.get_imdb_access_data_with_retry.side_effect = ImdbConnectionError("url", "Connection refused")
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
final_state = await imdb_enrich_ticket(make_state(request_context=_valid_context()))
|
|
|
|
assert final_state["error"]["type"] == "ImdbConnectionError"
|
|
assert final_state["current_step"] == "imdb_enrichment_failed"
|
|
|
|
@patch(IMDB_CLIENT_PATH)
|
|
async def test_imdb_enrichment_node_no_content_response(self, mock_client_cls):
|
|
"""204 No Content (None response) → step == 'imdb_enrichment_completed'."""
|
|
mock_client = AsyncMock()
|
|
mock_client.get_imdb_access_data_with_retry.return_value = None
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
final_state = await imdb_enrich_ticket(make_state(request_context=_valid_context()))
|
|
|
|
assert final_state["current_step"] == "imdb_enrichment_completed"
|
|
assert final_state["error"] is None
|
|
|
|
context = final_state["metadata"]["request_context"]
|
|
assert "imdb_access_data" in context
|
|
assert context["imdb_access_data"]["plan"] is None
|
|
assert context["imdb_access_data"]["status_code"] == 204
|
|
|
|
@patch(IMDB_CLIENT_PATH)
|
|
async def test_imdb_enrichment_node_generic_client_error(self, mock_client_cls):
|
|
"""Generic ImdbClientError → error.type == 'ImdbClientError'."""
|
|
mock_client = AsyncMock()
|
|
mock_client.get_imdb_access_data_with_retry.side_effect = ImdbClientError("Unexpected error")
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
final_state = await imdb_enrich_ticket(make_state(request_context=_valid_context()))
|
|
|
|
assert final_state["error"]["type"] == "ImdbClientError"
|
|
assert final_state["current_step"] == "imdb_enrichment_failed"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Router tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.mark.unit
|
|
class TestShouldContinue:
|
|
|
|
def test_should_continue_when_imdb_enrichment_completed(self):
|
|
state = make_state()
|
|
state["current_step"] = "imdb_enrichment_completed"
|
|
assert should_continue(state) == "continue"
|
|
|
|
def test_should_end_when_imdb_enrichment_failed(self):
|
|
state = make_state()
|
|
state["current_step"] = "imdb_enrichment_failed"
|
|
assert should_continue(state) == "failed"
|
|
|
|
def test_should_end_on_unknown_step(self):
|
|
state = make_state()
|
|
state["current_step"] = "some_other_step"
|
|
assert should_continue(state) == "failed"
|