feat: deliver versioned SIP config to Cells

This commit is contained in:
2026-09-14 14:38:15 +08:00
parent 3a47914a6c
commit c8716608a2
26 changed files with 4665 additions and 21 deletions
+32
View File
@@ -338,6 +338,38 @@ class AgentCallTests(unittest.TestCase):
"agent-call.commands.v1", routing_key("tenant#c"), {"message": 2}
)
def test_published_sip_directory_overrides_static_route(self) -> None:
class Directory:
def get_trunk(self, trunk_id: str) -> dict:
return {
"trunk_id": trunk_id,
"status": "published",
"revision": 7,
"config": {
"enabled": True,
"codec_profile": {
"allowed": ["PCMA"],
"preferred": "PCMA",
},
"caller_ids": ["BD93205882"],
"dial_prefix": "9001",
"egress_pool_id": "egress-mock",
"max_concurrency": 12,
"max_cps": 4,
"sip": {"transport": "udp"},
},
}
self.service.sip_readonly = Directory()
route = self.service._route_config("route_policy_test")
self.assertEqual(route["trunk_revision"], 7)
self.assertEqual(route["dial_prefix"], "9001")
self.assertEqual(route["max_concurrency"], 12)
cell = self.service._resource_cell(route)
self.assertIsNotNone(cell)
if cell is not None:
self.assertEqual(cell[0], "cell-a")
def test_profile_never_claims_real_ai(self) -> None:
metrics = self.service.metrics()
self.assertIn(
+344
View File
@@ -0,0 +1,344 @@
from __future__ import annotations
import hashlib
import json
import threading
import unittest
from http.client import HTTPConnection
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any
from urllib.parse import quote
from agent_call.cell_agent import (
AsteriskConfigApplier,
CellAgentError,
CellAgentService,
make_server,
)
from agent_call.sip_management import (
SipManagementError,
SipManagementService,
_validate_trunk,
)
class FakeApplier:
def __init__(self) -> None:
self.calls: list[tuple[str, dict[str, Any]]] = []
self.fail = False
def apply(self, trunk_id: str, config: dict[str, Any]) -> None:
if self.fail:
raise CellAgentError(502, "ASTERISK_RELOAD_FAILED", "reload failed")
self.calls.append((trunk_id, config))
class CellAgentTests(unittest.TestCase):
def test_revision_hash_idempotency_and_gap_protection(self) -> None:
with TemporaryDirectory() as directory:
applier = FakeApplier()
service = CellAgentService(
Path(directory) / "cell.sqlite3", "cell-a", applier
)
try:
body = self._publication("cell-a", "trunk:a", 1)
first = service.apply(body, "request-1")
second = service.apply(body, "request-1-retry")
self.assertFalse(first["idempotent"])
self.assertTrue(second["idempotent"])
self.assertEqual(len(applier.calls), 1)
with self.assertRaisesRegex(CellAgentError, "next revision"):
service.apply(
self._publication("cell-a", "trunk:a", 3), "request-3"
)
bad = self._publication("cell-a", "trunk:a", 2)
bad["config_sha256"] = "0" * 64
with self.assertRaisesRegex(CellAgentError, "does not match"):
service.apply(bad, "request-bad")
finally:
service.close()
def test_http_endpoint_unquotes_path_and_rejects_mismatch(self) -> None:
with TemporaryDirectory() as directory:
service = CellAgentService(
Path(directory) / "cell.sqlite3", "cell-a", FakeApplier()
)
server = make_server(service, "127.0.0.1", 0)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
body = self._publication("cell-a", "trunk:a", 1)
response = self._request(
server.server_port,
f"/v1/sip/trunks/{quote('trunk:a', safe='')}/apply",
body,
)
self.assertEqual(response.status, 200)
mismatch = self._publication("cell-a", "other", 2)
response = self._request(
server.server_port,
"/v1/sip/trunks/trunk%3Aa/apply",
mismatch,
)
self.assertEqual(response.status, 400)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=2)
service.close()
def test_asterisk_applier_writes_codec_and_restores_on_reload_failure(self) -> None:
with TemporaryDirectory() as directory:
class RecordingApplier(AsteriskConfigApplier):
def __init__(self, config_dir: str) -> None:
super().__init__(config_dir)
self.fail = False
def _reload(self) -> None:
if self.fail:
raise CellAgentError(
502, "ASTERISK_RELOAD_FAILED", "reload failed"
)
applier = RecordingApplier(directory)
config = self._config()
applier.apply("trunk:a", config)
target = next(Path(directory).glob("*.conf"))
original = target.read_text(encoding="utf-8")
self.assertIn("allow=alaw", original)
self.assertIn("from_user=BD93205882", original)
applier.fail = True
with self.assertRaises(CellAgentError):
applier.apply(
"trunk:a",
{
**config,
"codec_profile": {"allowed": ["PCMU"], "preferred": "PCMU"},
},
)
self.assertEqual(target.read_text(encoding="utf-8"), original)
def test_real_management_activates_only_after_cell_ack(self) -> None:
class Publisher:
def __init__(self) -> None:
self.fail = False
self.calls: list[int] = []
def publish(
self,
cell: dict[str, Any],
trunk_id: str,
revision: int,
config: dict[str, Any],
request_id: str,
) -> dict[str, Any]:
self.calls.append(revision)
if self.fail:
raise OSError("offline")
digest = hashlib.sha256(
json.dumps(
config,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode()
).hexdigest()
return {
"mode": "real",
"cell_id": cell["cell_id"],
"trunk_id": trunk_id,
"revision": revision,
"config_sha256": digest,
"status": "applied",
}
with TemporaryDirectory() as directory:
publisher = Publisher()
service = SipManagementService(
Path(directory) / "sip.sqlite3", mode="real", publisher=publisher
)
try:
service.upsert_cell(
"cell-a",
{
"egress_pool_id": "egress-a",
"codec_capabilities": ["PCMA"],
"status": "healthy",
"max_concurrency": 10,
"management_url": "https://cell-a.internal:9443",
},
expected_revision=0,
actor="ops",
request_id="cell-1",
)
service.upsert_trunk(
"trunk:a",
self._config(),
expected_revision=0,
actor="ops",
request_id="trunk-1",
)
published = service.publish_trunk(
"trunk:a", expected_revision=1, actor="ops", request_id="pub-1"
)
self.assertEqual(published["active_revision"], 1)
self.assertEqual(
service.list_publications("trunk:a")[0]["status"], "applied"
)
publisher.fail = True
service.upsert_trunk(
"trunk:a",
{**self._config(), "display_name": "Provider B"},
expected_revision=1,
actor="ops",
request_id="trunk-2",
)
with self.assertRaisesRegex(SipManagementError, "every Cell"):
service.publish_trunk(
"trunk:a", expected_revision=2, actor="ops", request_id="pub-2"
)
self.assertEqual(service.get_trunk("trunk:a")["active_revision"], 1)
self.assertEqual(
service.list_publications("trunk:a")[0]["status"], "applied"
)
self.assertEqual(
service.list_publications("trunk:a")[1]["status"], "failed"
)
finally:
service.close()
def test_real_publication_resumes_after_process_crash(self) -> None:
class CrashOncePublisher:
def __init__(self) -> None:
self.crashed = False
def publish(
self,
cell: dict[str, Any],
trunk_id: str,
revision: int,
config: dict[str, Any],
request_id: str,
) -> dict[str, Any]:
if not self.crashed:
self.crashed = True
raise KeyboardInterrupt
digest = hashlib.sha256(
json.dumps(
config,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode()
).hexdigest()
return {
"mode": "real",
"cell_id": cell["cell_id"],
"trunk_id": trunk_id,
"revision": revision,
"config_sha256": digest,
"status": "applied",
}
with TemporaryDirectory() as directory:
publisher = CrashOncePublisher()
service = SipManagementService(
Path(directory) / "sip.sqlite3", mode="real", publisher=publisher
)
try:
service.upsert_cell(
"cell-a",
{
"egress_pool_id": "egress-a",
"codec_capabilities": ["PCMA"],
"status": "healthy",
"max_concurrency": 10,
"management_url": "https://cell-a.internal:9443",
},
expected_revision=0,
actor="ops",
request_id="cell-1",
)
service.upsert_trunk(
"trunk:a",
self._config(),
expected_revision=0,
actor="ops",
request_id="trunk-1",
)
with self.assertRaises(KeyboardInterrupt):
service.publish_trunk(
"trunk:a", expected_revision=1, actor="ops", request_id="pub-1"
)
self.assertEqual(service.get_trunk("trunk:a")["active_revision"], 0)
resumed = service.publish_trunk(
"trunk:a", expected_revision=1, actor="ops", request_id="pub-2"
)
self.assertEqual(resumed["active_revision"], 1)
self.assertEqual(
service.list_publications("trunk:a")[0]["status"], "applied"
)
finally:
service.close()
@staticmethod
def _config() -> dict[str, Any]:
return {
"display_name": "Provider A",
"enabled": True,
"sip": {
"host": "61.132.228.221",
"port": 5060,
"transport": "udp",
"auth_mode": "ip",
"register": False,
},
"codec_profile": {"allowed": ["PCMA"], "preferred": "PCMA"},
"caller_ids": ["BD93205882"],
"dial_prefix": "7089",
"egress_pool_id": "egress-a",
"max_concurrency": 100,
"max_cps": 10,
}
@classmethod
def _publication(cls, cell_id: str, trunk_id: str, revision: int) -> dict[str, Any]:
config = _validate_trunk(trunk_id, cls._config())
digest = hashlib.sha256(
json.dumps(
config, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode()
).hexdigest()
return {
"mode": "real",
"cell_id": cell_id,
"trunk_id": trunk_id,
"revision": revision,
"config": config,
"config_sha256": digest,
}
@staticmethod
def _request(port: int, path: str, body: dict[str, Any]):
encoded = json.dumps(body, ensure_ascii=False).encode()
connection = HTTPConnection("127.0.0.1", port, timeout=3)
connection.request(
"POST",
path,
body=encoded,
headers={
"Content-Type": "application/json",
"Content-Length": str(len(encoded)),
"X-Request-ID": "http-test",
},
)
response = connection.getresponse()
response.read()
connection.close()
return response
if __name__ == "__main__":
unittest.main()
+14
View File
@@ -22,8 +22,22 @@ class ContractTests(unittest.TestCase):
encoding="utf-8"
)
saas = (ROOT / "docs/contracts/saas.openapi.yaml").read_text(encoding="utf-8")
sip_management = (
ROOT / "docs/contracts/sip-management.openapi.yaml"
).read_text(encoding="utf-8")
self.assertIn("openapi: 3.1.0", executor)
self.assertIn("openapi: 3.1.0", saas)
cell_agent = (ROOT / "docs/contracts/cell-agent.openapi.yaml").read_text(
encoding="utf-8"
)
self.assertIn("openapi: 3.1.0", sip_management)
self.assertIn("openapi: 3.1.0", cell_agent)
self.assertIn("CellManagementMtls", cell_agent)
self.assertIn("/v1/sip/trunks/{trunk_id}/apply:", cell_agent)
self.assertIn("SipAdminBearer", sip_management)
self.assertIn("SaasTrunkReadBearer", sip_management)
self.assertIn("/admin/v1/trunks/{trunk_id}/publish:", sip_management)
self.assertIn("/readonly/v1/sip/trunks:", sip_management)
self.assertNotIn("call.execute", executor)
self.assertIn("/internal/v1/outbound/tasks/{task_id}/controls:", executor)
self.assertIn(
+26 -2
View File
@@ -247,8 +247,16 @@ class AsteriskTests(unittest.TestCase):
"public_ip": cloud.PUBLIC_IP,
"transport": "udp",
"local_net": "10.1.0.0/16",
"primary": {"host": "sip-a.test", "auth_mode": "ip"},
"backup": {"host": "sip-b.test", "auth_mode": "ip"},
"primary": {
"host": "sip-a.test",
"auth_mode": "ip",
"codec_profile": {"allowed": ["PCMA"], "preferred": "PCMA"},
},
"backup": {
"host": "sip-b.test",
"auth_mode": "ip",
"codec_profile": {"allowed": ["PCMA"], "preferred": "PCMA"},
},
}
def test_build_user_data_pins_image_and_does_not_print_secret(self):
@@ -258,11 +266,27 @@ class AsteriskTests(unittest.TestCase):
"x" * 32,
)
self.assertIn("ASTERISK_IMAGE=registry.example/asterisk@sha256:", content)
self.assertIn("pjsip set logger on", content)
self.assertIn("HTTP_CONF_B64=", content)
self.assertNotIn("password=" + "x" * 32, content)
with self.assertRaises(ValueError):
user_data.immutable_image("registry.example/asterisk:latest")
def test_codec_profile_is_rendered_without_global_fallback(self):
cfg = self.cfg()
cfg["primary"]["codec_profile"] = {
"allowed": ["PCMU", "PCMA"],
"preferred": "PCMA",
}
files = ast.render(cfg, {"ARI_PASSWORD": "x" * 32})
self.assertIn("allow=alaw,ulaw", files["pjsip.conf"])
cfg["primary"]["codec_profile"] = {
"allowed": ["G729"],
"preferred": "G729",
}
with self.assertRaises(ValueError):
ast.render(cfg, {"ARI_PASSWORD": "x" * 32})
def test_private_ari_fixed_nat_and_recording_config(self):
files = ast.render(self.cfg(), {"ARI_PASSWORD": "x" * 32})
self.assertEqual(len(files), 5)
+456
View File
@@ -0,0 +1,456 @@
from __future__ import annotations
import json
import threading
import unittest
from http.client import HTTPConnection
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any
from urllib.parse import urlsplit
from agent_call.sip_management import (
ADMIN_AUDIENCE,
READ_AUDIENCE,
ConfigurationError,
Principal,
SipManagementService,
load_token_map,
make_server,
validate_token_separation,
)
ROOT = Path(__file__).resolve().parents[1]
ADMIN_BEARER = "admin-test"
READ_BEARER = "saas-test"
class SipManagementTests(unittest.TestCase):
def test_codec_mapping_and_token_domains_are_separate(self) -> None:
admin = load_token_map(
json.dumps(
{
ADMIN_BEARER: {
"subject": "ops",
"issuer": "ops-issuer",
"audience": ADMIN_AUDIENCE,
"scopes": ["*"],
"trunk_ids": "*",
}
}
),
"admin",
)
read = load_token_map(
json.dumps(
{
READ_BEARER: {
"subject": "saas",
"issuer": "saas-issuer",
"audience": READ_AUDIENCE,
"scopes": ["sip.trunk.read"],
"trunk_ids": ["trunk-a"],
}
}
),
"read",
)
validate_token_separation(admin, read, {"scheduler-test": {}})
self.assertNotEqual(set(admin), set(read))
with self.assertRaises(ConfigurationError):
validate_token_separation(admin, read, {ADMIN_BEARER: {}})
def test_persistent_versioned_publish_readonly_and_rollback(self) -> None:
with TemporaryDirectory() as directory:
service = SipManagementService(Path(directory) / "sip.sqlite3")
try:
service.upsert_cell(
"cell-a",
{
"egress_pool_id": "egress-a",
"codec_capabilities": ["PCMA", "PCMU"],
"status": "healthy",
"max_concurrency": 100,
},
expected_revision=0,
actor="ops",
request_id="cell-1",
)
created = service.upsert_trunk(
"trunk-a",
self._trunk_payload(["PCMA"]),
expected_revision=0,
actor="ops",
request_id="trunk-1",
)
self.assertEqual(created["latest_revision"], 1)
published = service.publish_trunk(
"trunk-a", expected_revision=1, actor="ops", request_id="publish-1"
)
self.assertEqual(published["status"], "published")
self.assertEqual(published["compatible_cell_ids"], ["cell-a"])
self.assertEqual(
service.list_publications("trunk-a")[0]["status"], "pending"
)
readonly = service.get_readonly_trunk("trunk-a", frozenset({"trunk-a"}))
self.assertEqual(
readonly["config"]["codec_profile"]["allowed"], ["PCMA"]
)
self.assertNotIn("credential_ref", readonly["config"]["sip"])
updated = service.upsert_trunk(
"trunk-a",
self._trunk_payload(["PCMU"]),
expected_revision=1,
actor="ops",
request_id="trunk-2",
)
self.assertEqual(updated["latest_revision"], 2)
with self.assertRaisesRegex(Exception, "revision changed"):
service.publish_trunk(
"trunk-a",
expected_revision=1,
actor="ops",
request_id="publish-old",
)
service.publish_trunk(
"trunk-a", expected_revision=2, actor="ops", request_id="publish-2"
)
rolled_back = service.rollback_trunk(
"trunk-a",
1,
expected_revision=2,
actor="ops",
request_id="rollback-1",
)
self.assertEqual(rolled_back["active_revision"], 3)
self.assertEqual(
rolled_back["active"]["codec_profile"]["allowed"], ["PCMA"]
)
self.assertEqual(
[entry["action"] for entry in service.list_audit("trunk-a")],
["upsert", "publish", "upsert", "publish", "rollback"],
)
finally:
service.close()
def test_state_survives_restart_and_secret_refs_are_not_exposed(self) -> None:
with TemporaryDirectory() as directory:
database = Path(directory) / "sip.sqlite3"
service = SipManagementService(database)
service.upsert_cell(
"cell-a",
{
"egress_pool_id": "egress-a",
"codec_capabilities": ["PCMA"],
"status": "healthy",
"max_concurrency": 10,
},
expected_revision=0,
actor="ops",
request_id="cell-1",
)
service.upsert_trunk(
"trunk-a",
{
**self._trunk_payload(["PCMA"]),
"sip": {
**self._trunk_payload(["PCMA"])["sip"],
"auth_mode": "digest",
"credential_ref": "secret://provider-a",
},
},
expected_revision=0,
actor="ops",
request_id="trunk-1",
)
service.publish_trunk(
"trunk-a", expected_revision=1, actor="ops", request_id="publish-1"
)
service.close()
reopened = SipManagementService(database)
try:
admin = reopened.get_trunk("trunk-a")
readonly = reopened.get_readonly_trunk(
"trunk-a", frozenset({"trunk-a"})
)
self.assertTrue(admin["active"]["credential_configured"])
self.assertNotIn("credential_ref", admin["active"]["sip"])
self.assertNotIn("credential_ref", readonly["config"]["sip"])
finally:
reopened.close()
def test_openapi_separates_admin_and_saas_readonly_surfaces(self) -> None:
contract = (ROOT / "docs/contracts/sip-management.openapi.yaml").read_text(
encoding="utf-8"
)
self.assertIn("SipAdminBearer", contract)
self.assertIn("SaasTrunkReadBearer", contract)
self.assertIn("/admin/v1/trunks/{trunk_id}/publish:", contract)
self.assertIn("/readonly/v1/sip/trunks:", contract)
self.assertNotIn("HTTP_TOKENS", contract)
def test_publish_rejects_without_compatible_cell(self) -> None:
with TemporaryDirectory() as directory:
service = SipManagementService(Path(directory) / "sip.sqlite3")
try:
service.upsert_cell(
"cell-pcmu",
{
"egress_pool_id": "egress-a",
"codec_capabilities": ["PCMU"],
"status": "healthy",
"max_concurrency": 10,
},
expected_revision=0,
actor="ops",
request_id="cell-1",
)
service.upsert_trunk(
"trunk-a",
self._trunk_payload(["PCMA"]),
expected_revision=0,
actor="ops",
request_id="trunk-1",
)
with self.assertRaisesRegex(Exception, "no healthy Cell"):
service.publish_trunk(
"trunk-a",
expected_revision=1,
actor="ops",
request_id="publish-1",
)
finally:
service.close()
def test_http_readonly_token_cannot_write_or_read_admin_api(self) -> None:
with TemporaryDirectory() as directory:
service = SipManagementService(Path(directory) / "sip.sqlite3")
admin_tokens = {
ADMIN_BEARER: Principal(
"ops",
"ops-issuer",
ADMIN_AUDIENCE,
frozenset({"*"}),
frozenset({"*"}),
"admin",
)
}
read_tokens = {
READ_BEARER: Principal(
"saas",
"saas-issuer",
READ_AUDIENCE,
frozenset({"sip.trunk.read"}),
frozenset({"trunk-a"}),
"read",
)
}
server = make_server(
service,
"127.0.0.1",
0,
admin_tokens=admin_tokens,
read_tokens=read_tokens,
)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
base = f"http://127.0.0.1:{server.server_port}"
try:
status, body = self._request(
base,
"GET",
"/healthz/live",
bearer=None,
)
self.assertEqual(status, 200)
self.assertEqual(body["mode"], "mock")
status, _ = self._request(
base,
"PUT",
"/admin/v1/trunks/trunk-a",
bearer=READ_BEARER,
headers={"If-Match": "0", "X-Request-ID": "write-1"},
body=self._trunk_payload(["PCMA"]),
)
self.assertEqual(status, 401)
status, _ = self._request(
base,
"GET",
"/readonly/v1/sip/trunks",
bearer=ADMIN_BEARER,
)
self.assertEqual(status, 401)
status, _ = self._request(
base,
"POST",
"/readonly/v1/sip/trunks/trunk-a",
bearer=READ_BEARER,
body={},
)
self.assertEqual(status, 405)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=2)
service.close()
def test_http_admin_publish_and_readonly_uses_active_revision(self) -> None:
with TemporaryDirectory() as directory:
service = SipManagementService(Path(directory) / "sip.sqlite3")
admin_tokens = {
ADMIN_BEARER: Principal(
"ops",
"ops-issuer",
ADMIN_AUDIENCE,
frozenset({"*"}),
frozenset({"*"}),
"admin",
)
}
read_tokens = {
READ_BEARER: Principal(
"saas",
"saas-issuer",
READ_AUDIENCE,
frozenset({"sip.trunk.read"}),
frozenset({"trunk-a"}),
"read",
)
}
server = make_server(
service,
"127.0.0.1",
0,
admin_tokens=admin_tokens,
read_tokens=read_tokens,
)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
base = f"http://127.0.0.1:{server.server_port}"
try:
status, _ = self._request(
base,
"PUT",
"/admin/v1/cells/cell-a",
bearer=ADMIN_BEARER,
headers={"If-Match": "0", "X-Request-ID": "cell-1"},
body={
"egress_pool_id": "egress-a",
"codec_capabilities": ["PCMA", "PCMU"],
"status": "healthy",
"max_concurrency": 10,
},
)
self.assertEqual(status, 201)
status, _ = self._request(
base,
"PUT",
"/admin/v1/trunks/trunk-a",
bearer=ADMIN_BEARER,
headers={"If-Match": "0", "X-Request-ID": "trunk-1"},
body=self._trunk_payload(["PCMA"]),
)
self.assertEqual(status, 201)
status, _ = self._request(
base,
"POST",
"/admin/v1/trunks/trunk-a/publish",
bearer=ADMIN_BEARER,
headers={"If-Match": "1", "X-Request-ID": "publish-1"},
)
self.assertEqual(status, 200)
status, body = self._request(
base,
"GET",
"/readonly/v1/sip/trunks/trunk-a",
bearer=READ_BEARER,
)
self.assertEqual(status, 200)
self.assertEqual(body["revision"], 1)
self.assertEqual(body["config"]["codec_profile"]["allowed"], ["PCMA"])
self.assertNotIn("latest", body)
self.assertNotIn("compatible_cell_ids", body)
status, _ = self._request(
base,
"PUT",
"/admin/v1/trunks/trunk-a",
bearer=ADMIN_BEARER,
headers={"If-Match": "1", "X-Request-ID": "trunk-2"},
body=self._trunk_payload(["PCMU"]),
)
self.assertEqual(status, 200)
status, body = self._request(
base,
"GET",
"/readonly/v1/sip/trunks/trunk-a",
bearer=READ_BEARER,
)
self.assertEqual(status, 200)
self.assertEqual(body["revision"], 1)
self.assertEqual(body["config"]["codec_profile"]["allowed"], ["PCMA"])
finally:
server.shutdown()
server.server_close()
thread.join(timeout=2)
service.close()
@staticmethod
def _trunk_payload(codecs: list[str]) -> dict[str, Any]:
return {
"display_name": "Provider A",
"enabled": True,
"sip": {
"host": "61.132.228.221",
"port": 5060,
"transport": "udp",
"auth_mode": "ip",
"register": False,
},
"codec_profile": {"allowed": codecs, "preferred": codecs[0]},
"caller_ids": ["BD93205882"],
"dial_prefix": "7089",
"egress_pool_id": "egress-a",
"max_concurrency": 100,
"max_cps": 10,
}
@staticmethod
def _request(
base: str,
method: str,
path: str,
*,
bearer: str | None,
headers: dict[str, str] | None = None,
body: Any | None = None,
) -> tuple[int, dict[str, Any]]:
request_headers = {"Accept": "application/json"}
if bearer:
request_headers["Authorization"] = f"Bearer {bearer}"
if headers:
request_headers.update(headers)
data = None
if body is not None:
data = json.dumps(body).encode("utf-8")
request_headers["Content-Type"] = "application/json"
parsed = urlsplit(base)
host = parsed.hostname
port = parsed.port
if parsed.scheme != "http" or host != "127.0.0.1" or port is None:
raise AssertionError("test helper only permits a local HTTP server")
connection = HTTPConnection(host, port, timeout=3)
try:
connection.request(method, path, body=data, headers=request_headers)
response = connection.getresponse()
return response.status, json.loads(response.read())
finally:
connection.close()
if __name__ == "__main__":
unittest.main()