import json from sqlalchemy import select from services.routing_service import engine as routing_engine from services.shared.core import new_id, utc_now_iso from services.shared.db import get_session from services.shared.sql_init import init_sql_schema from services.shared.sql_models import AgentRow def _make_agent(session, *, level="L2", tenant_ids=None, skills=None, status="AVAILABLE", enabled=True): now = utc_now_iso() row = AgentRow( agent_id=new_id("agt"), tenant_ids_json=json.dumps(tenant_ids or []), extension="2001", endpoint=None, display_name="Test Agent", level=level, skills_json=json.dumps(skills or []), status=status, max_concurrent_calls=1, enabled=enabled, created_at=now, updated_at=now, ) session.add(row) session.commit() session.refresh(row) return row def test_reserve_agent_picks_available_agent_of_requested_level(): init_sql_schema() session = get_session() try: agent_l2 = _make_agent(session, level="L2") _make_agent(session, level="L3") reserved = routing_engine.reserve_agent( session, call_id="call-1", level="L2", tenant_id=None, required_skills=[], ) assert reserved is not None assert reserved.agent_id == agent_l2.agent_id assert reserved.status == "RESERVED" assert reserved.current_call_id == "call-1" finally: session.close() def test_reserve_agent_does_not_double_reserve(): init_sql_schema() session = get_session() try: _make_agent(session, level="L2") first = routing_engine.reserve_agent(session, call_id="call-a", level="L2", tenant_id=None, required_skills=[]) second = routing_engine.reserve_agent(session, call_id="call-b", level="L2", tenant_id=None, required_skills=[]) assert first is not None assert second is None finally: session.close() def test_reserve_agent_filters_by_tenant_and_skills(): init_sql_schema() session = get_session() try: wrong_tenant = _make_agent(session, level="L2", tenant_ids=["other"]) no_skill = _make_agent(session, level="L2", tenant_ids=["konturai"], skills=[]) matching = _make_agent(session, level="L2", tenant_ids=["konturai"], skills=["billing"]) reserved = routing_engine.reserve_agent( session, call_id="call-skill", level="L2", tenant_id="konturai", required_skills=["billing"], ) assert reserved is not None assert reserved.agent_id == matching.agent_id assert reserved.agent_id != wrong_tenant.agent_id assert reserved.agent_id != no_skill.agent_id finally: session.close() def test_release_agent_by_call_id_makes_agent_available_again(): init_sql_schema() session = get_session() try: agent = _make_agent(session, level="L2") reserved = routing_engine.reserve_agent(session, call_id="call-z", level="L2", tenant_id=None, required_skills=[]) assert reserved is not None released = routing_engine.release_agent_by_call_id(session, call_id="call-z") assert released is not None assert released.agent_id == agent.agent_id assert released.status == "AVAILABLE" assert released.current_call_id is None assert released.calls_handled_count == 1 again = routing_engine.reserve_agent(session, call_id="call-again", level="L2", tenant_id=None, required_skills=[]) assert again is not None assert again.agent_id == agent.agent_id finally: session.close() def test_reserve_agent_excludes_disabled_and_excluded_ids(): init_sql_schema() session = get_session() try: disabled = _make_agent(session, level="L2", enabled=False) excluded = _make_agent(session, level="L2") available = _make_agent(session, level="L2") reserved = routing_engine.reserve_agent( session, call_id="call-exc", level="L2", tenant_id=None, required_skills=[], exclude_agent_ids=[excluded.agent_id], ) assert reserved is not None assert reserved.agent_id == available.agent_id assert reserved.agent_id != disabled.agent_id assert reserved.agent_id != excluded.agent_id finally: session.close()