dograh/api/tests/telephony/test_external_pbx_configuration.py

81 lines
2.4 KiB
Python
Raw Permalink Normal View History

feat: add vici dial controls from UI (#563) * ViciDial Working * feat(telephony): add FreeSWITCH provider to upstream-PBX seam Generalize the upstream-PBX capture and control paths to dispatch by provider. FreeSWITCH bridges in with X-PBX-* headers and is driven over the Event Socket Library (uuid_kill / uuid_transfer) by the channel UUID, alongside the existing VICIdial ra_call_control path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * add vici specific configs * feat(telephony): env-based VICIdial config + address3 post-call routing Move VICIdial agent/non-agent API and FreeSWITCH ESL connection settings out of hardcoded POC values into environment variables so the same image works against a PBX on another server. Add VICIdial update_lead forwarding: X-VICI-UPDATE-LEAD_* extracted variables are mapped to lead columns and pushed via the non-agent API before a transfer, with reserved API-control params dropped. Add hardcoded address3 disposition routing (Y/N -> in-group transfer, else hang up) and a synchronous final variable extraction so the transfer path sees the freshest conversation state. Skip upstream_pbx capture on non-PJSIP channels to avoid Asterisk 500s. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: run formatter * feat: make vici configurable from UI * chore: clean up PR * fix: incorporate review comments * chore: incorporate review comments * chore: generate client * chore: incporporate review comments * chore: incorporate review comments --------- Co-authored-by: Dograh POC <payment@dograh.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:57:34 +05:30
from unittest.mock import AsyncMock
import pytest
from api.routes import organization
from api.services import tool_management
def _credentials(password: str = "agent-secret") -> dict:
return {
"ari_endpoint": "https://asterisk.example.com",
"app_name": "dograh",
"app_password": "ari-secret",
"external_pbx": {
"type": "vicidial",
"agent_api": {
"url": "https://vici.example.com/agc/api.php",
"username": "agent-user",
"password": password,
},
},
}
def test_nested_external_pbx_secrets_are_masked_without_mutating_source():
credentials = _credentials()
masked = organization._mask_sensitive("ari", credentials)
assert masked["app_password"] != "ari-secret"
assert masked["external_pbx"]["agent_api"]["password"] != "agent-secret"
assert credentials["external_pbx"]["agent_api"]["password"] == "agent-secret"
def test_nested_masked_external_pbx_secrets_are_restored_on_update():
existing = _credentials()
request = organization._mask_sensitive("ari", existing)
organization.preserve_masked_fields("ari", request, existing)
assert request["app_password"] == "ari-secret"
assert request["external_pbx"]["agent_api"]["password"] == "agent-secret"
@pytest.mark.asyncio
async def test_disabled_feature_preserves_existing_tool_mapping(monkeypatch):
monkeypatch.setattr(
tool_management,
"external_pbx_integrations_enabled",
AsyncMock(return_value=False),
)
definition = {
"type": "transfer_call",
"config": {
"destination_source": "context_mapping",
"context_mapping": {
"context_path": "qualified",
"routes": [{"context_value": "yes", "destination": "sales"}],
},
},
}
await tool_management.validate_external_pbx_tool_definition(
definition,
organization_id=7,
existing_definition=definition,
)
changed = {
"type": "transfer_call",
"config": {"destination_source": "static", "destination": "+15555550100"},
}
with pytest.raises(tool_management.ToolManagementError) as exc_info:
await tool_management.validate_external_pbx_tool_definition(
changed,
organization_id=7,
existing_definition=definition,
)
assert exc_info.value.status_code == 403