You've already forked directdnsonly
Adds get_extra_dns_servers(), add_extra_dns_server(), and the high-level ensure_extra_dns_server() which registers a node and enforces dns=yes + domain_check=yes in a single call. Also adds the generic post() helper. 10 new tests, 141 total.
371 lines
11 KiB
Python
371 lines
11 KiB
Python
"""Tests for directdnsonly.app.da.client — DirectAdminClient."""
|
|
|
|
import requests.exceptions
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from directdnsonly.app.da import DirectAdminClient
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_json_response(domains_list, total_pages=1):
|
|
data = {str(i): {"domain": d} for i, d in enumerate(domains_list)}
|
|
data["info"] = {"total_pages": total_pages}
|
|
mock = MagicMock()
|
|
mock.status_code = 200
|
|
mock.is_redirect = False
|
|
mock.headers = {"Content-Type": "application/json"}
|
|
mock.json.return_value = data
|
|
mock.raise_for_status = MagicMock()
|
|
return mock
|
|
|
|
|
|
def _client():
|
|
return DirectAdminClient(
|
|
"da1.example.com", 2222, "admin", "secret", ssl=True, verify_ssl=True
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# list_domains — JSON happy path
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_list_domains_returns_set_from_json():
|
|
mock_resp = _make_json_response(["example.com", "test.com"])
|
|
|
|
with patch("requests.get", return_value=mock_resp):
|
|
result = _client().list_domains()
|
|
|
|
assert result == {"example.com", "test.com"}
|
|
|
|
|
|
def test_list_domains_paginates():
|
|
page1 = _make_json_response(["a.com"], total_pages=2)
|
|
page2 = _make_json_response(["b.com"], total_pages=2)
|
|
|
|
with patch("requests.get", side_effect=[page1, page2]):
|
|
result = _client().list_domains()
|
|
|
|
assert result == {"a.com", "b.com"}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# list_domains — DA Evo session login fallback
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_redirect_triggers_session_login():
|
|
redirect_resp = MagicMock()
|
|
redirect_resp.status_code = 302
|
|
redirect_resp.is_redirect = True
|
|
|
|
client = _client()
|
|
with (
|
|
patch("requests.get", return_value=redirect_resp),
|
|
patch.object(client, "_login", return_value=False),
|
|
):
|
|
result = client.list_domains()
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_persistent_redirect_after_login_returns_none():
|
|
redirect_resp = MagicMock()
|
|
redirect_resp.status_code = 302
|
|
redirect_resp.is_redirect = True
|
|
|
|
client = _client()
|
|
# Simulate cookies already set (login succeeded previously)
|
|
client._cookies = {"session": "abc"}
|
|
|
|
with patch("requests.get", return_value=redirect_resp):
|
|
result = client.list_domains()
|
|
|
|
assert result is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# list_domains — error cases
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_html_response_returns_none():
|
|
mock_resp = MagicMock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.is_redirect = False
|
|
mock_resp.headers = {"Content-Type": "text/html; charset=utf-8"}
|
|
mock_resp.raise_for_status = MagicMock()
|
|
|
|
with patch("requests.get", return_value=mock_resp):
|
|
result = _client().list_domains()
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_connection_error_returns_none():
|
|
with patch(
|
|
"requests.get", side_effect=requests.exceptions.ConnectionError("refused")
|
|
):
|
|
result = _client().list_domains()
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_timeout_returns_none():
|
|
with patch("requests.get", side_effect=requests.exceptions.Timeout()):
|
|
result = _client().list_domains()
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_ssl_error_returns_none():
|
|
with patch(
|
|
"requests.get", side_effect=requests.exceptions.SSLError("cert verify failed")
|
|
):
|
|
result = _client().list_domains()
|
|
|
|
assert result is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _parse_legacy_domain_list
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_parse_standard_querystring():
|
|
result = DirectAdminClient._parse_legacy_domain_list(
|
|
"list[]=example.com&list[]=test.com"
|
|
)
|
|
assert result == {"example.com", "test.com"}
|
|
|
|
|
|
def test_parse_newline_separated():
|
|
result = DirectAdminClient._parse_legacy_domain_list(
|
|
"list[]=example.com\nlist[]=test.com"
|
|
)
|
|
assert result == {"example.com", "test.com"}
|
|
|
|
|
|
def test_parse_empty_body_returns_empty_set():
|
|
assert DirectAdminClient._parse_legacy_domain_list("") == set()
|
|
|
|
|
|
def test_parse_normalises_to_lowercase():
|
|
result = DirectAdminClient._parse_legacy_domain_list("list[]=EXAMPLE.COM")
|
|
assert "example.com" in result
|
|
assert "EXAMPLE.COM" not in result
|
|
|
|
|
|
def test_parse_strips_whitespace():
|
|
result = DirectAdminClient._parse_legacy_domain_list("list[]= example.com ")
|
|
assert "example.com" in result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _login
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_login_stores_cookies_on_success():
|
|
mock_resp = MagicMock()
|
|
mock_resp.cookies = {"session": "tok123"}
|
|
|
|
client = _client()
|
|
with patch("requests.post", return_value=mock_resp):
|
|
result = client._login()
|
|
|
|
assert result is True
|
|
assert client._cookies == {"session": "tok123"}
|
|
|
|
|
|
def test_login_returns_false_when_no_cookies():
|
|
mock_resp = MagicMock()
|
|
mock_resp.cookies = {}
|
|
|
|
client = _client()
|
|
with patch("requests.post", return_value=mock_resp):
|
|
result = client._login()
|
|
|
|
assert result is False
|
|
assert client._cookies is None
|
|
|
|
|
|
def test_login_returns_false_on_exception():
|
|
client = _client()
|
|
with patch("requests.post", side_effect=requests.exceptions.ConnectionError()):
|
|
result = client._login()
|
|
|
|
assert result is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_extra_dns_servers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _multi_server_get_resp(servers=None):
|
|
mock = MagicMock()
|
|
mock.status_code = 200
|
|
mock.is_redirect = False
|
|
mock.headers = {"Content-Type": "application/json"}
|
|
mock.json.return_value = {"CLUSTER_ON": "yes", "servers": servers or {}}
|
|
mock.raise_for_status = MagicMock()
|
|
return mock
|
|
|
|
|
|
def test_get_extra_dns_servers_returns_servers_dict():
|
|
servers = {
|
|
"1.2.3.4": {"dns": "yes", "domain_check": "yes", "port": "2222", "ssl": "no"}
|
|
}
|
|
with patch("requests.get", return_value=_multi_server_get_resp(servers)):
|
|
result = _client().get_extra_dns_servers()
|
|
|
|
assert "1.2.3.4" in result
|
|
assert result["1.2.3.4"]["dns"] == "yes"
|
|
|
|
|
|
def test_get_extra_dns_servers_returns_empty_on_http_error():
|
|
mock_resp = MagicMock()
|
|
mock_resp.status_code = 500
|
|
with patch("requests.get", return_value=mock_resp):
|
|
result = _client().get_extra_dns_servers()
|
|
|
|
assert result == {}
|
|
|
|
|
|
def test_get_extra_dns_servers_returns_empty_on_connection_error():
|
|
with patch(
|
|
"requests.get", side_effect=requests.exceptions.ConnectionError("refused")
|
|
):
|
|
result = _client().get_extra_dns_servers()
|
|
|
|
assert result == {}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# add_extra_dns_server
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_add_extra_dns_server_returns_true_on_success():
|
|
mock_resp = MagicMock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.json.return_value = {"result": "", "success": "Connection Added"}
|
|
|
|
with patch("requests.post", return_value=mock_resp):
|
|
result = _client().add_extra_dns_server("1.2.3.4", 2222, "ddnsonly", "s3cr3t")
|
|
|
|
assert result is True
|
|
|
|
|
|
def test_add_extra_dns_server_returns_false_on_da_error():
|
|
mock_resp = MagicMock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.json.return_value = {"result": "Server already exists", "success": ""}
|
|
|
|
with patch("requests.post", return_value=mock_resp):
|
|
result = _client().add_extra_dns_server("1.2.3.4", 2222, "ddnsonly", "s3cr3t")
|
|
|
|
assert result is False
|
|
|
|
|
|
def test_add_extra_dns_server_returns_false_on_connection_error():
|
|
with patch(
|
|
"requests.post", side_effect=requests.exceptions.ConnectionError("refused")
|
|
):
|
|
result = _client().add_extra_dns_server("1.2.3.4", 2222, "ddnsonly", "s3cr3t")
|
|
|
|
assert result is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ensure_extra_dns_server
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _add_success_resp():
|
|
mock = MagicMock()
|
|
mock.status_code = 200
|
|
mock.json.return_value = {"result": "", "success": "Connection Added"}
|
|
return mock
|
|
|
|
|
|
def _save_success_resp():
|
|
mock = MagicMock()
|
|
mock.status_code = 200
|
|
mock.json.return_value = {"result": "", "success": "Connections Saved"}
|
|
return mock
|
|
|
|
|
|
def test_ensure_extra_dns_server_adds_and_configures_new_server():
|
|
"""Server not yet registered — adds it, then saves dns+domain_check settings."""
|
|
with (
|
|
patch("requests.get", return_value=_multi_server_get_resp(servers={})),
|
|
patch(
|
|
"requests.post",
|
|
side_effect=[_add_success_resp(), _save_success_resp()],
|
|
),
|
|
):
|
|
result = _client().ensure_extra_dns_server(
|
|
"1.2.3.4", 2222, "ddnsonly", "s3cr3t"
|
|
)
|
|
|
|
assert result is True
|
|
|
|
|
|
def test_ensure_extra_dns_server_skips_add_when_already_present():
|
|
"""Server already registered — no add call, only saves settings."""
|
|
existing = {
|
|
"1.2.3.4": {"dns": "no", "domain_check": "no", "port": "2222", "ssl": "no"}
|
|
}
|
|
with (
|
|
patch("requests.get", return_value=_multi_server_get_resp(servers=existing)),
|
|
patch("requests.post", return_value=_save_success_resp()) as mock_post,
|
|
):
|
|
result = _client().ensure_extra_dns_server(
|
|
"1.2.3.4", 2222, "ddnsonly", "s3cr3t"
|
|
)
|
|
|
|
assert result is True
|
|
assert mock_post.call_count == 1 # save only, no add
|
|
|
|
|
|
def test_ensure_extra_dns_server_returns_false_when_add_fails():
|
|
fail_resp = MagicMock()
|
|
fail_resp.status_code = 200
|
|
fail_resp.json.return_value = {"result": "error", "success": ""}
|
|
|
|
with (
|
|
patch("requests.get", return_value=_multi_server_get_resp(servers={})),
|
|
patch("requests.post", return_value=fail_resp),
|
|
):
|
|
result = _client().ensure_extra_dns_server(
|
|
"1.2.3.4", 2222, "ddnsonly", "s3cr3t"
|
|
)
|
|
|
|
assert result is False
|
|
|
|
|
|
def test_ensure_extra_dns_server_returns_false_when_save_fails():
|
|
"""Add succeeds but the subsequent settings save fails."""
|
|
fail_save = MagicMock()
|
|
fail_save.status_code = 200
|
|
fail_save.json.return_value = {"result": "error", "success": ""}
|
|
|
|
with (
|
|
patch("requests.get", return_value=_multi_server_get_resp(servers={})),
|
|
patch(
|
|
"requests.post",
|
|
side_effect=[_add_success_resp(), fail_save],
|
|
),
|
|
):
|
|
result = _client().ensure_extra_dns_server(
|
|
"1.2.3.4", 2222, "ddnsonly", "s3cr3t"
|
|
)
|
|
|
|
assert result is False
|