From b1bae87787573eadcfc352d949ba96dddbaf1140 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 20 Jul 2026 10:32:31 -0400 Subject: [PATCH] test: cover DNS entrypoint discovery --- tests/integration/test_live_nodes.py | 45 ++++++++++++++++++++++++++++ tests/unit/test_sync_manager.py | 26 ++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/tests/integration/test_live_nodes.py b/tests/integration/test_live_nodes.py index 16d8ea2..46c60d8 100644 --- a/tests/integration/test_live_nodes.py +++ b/tests/integration/test_live_nodes.py @@ -2,10 +2,15 @@ from __future__ import annotations +from collections.abc import Sequence +from urllib.parse import urlsplit + import pytest from alternator import AlternatorConfigBuilder, Auth, Helper, KeyRouteAffinityMode +from alternator._http import create_sync_http_fetcher from alternator.async_client import AsyncHelper +from tests.conftest import FakeAlternatorServer from tests.integration import SCYLLA_HOST, SCYLLA_PORT, SKIP_INTEGRATION pytestmark = [ @@ -27,6 +32,19 @@ def _affinity_config() -> object: ) +def _live_cluster_localnodes() -> Sequence[str]: + fetcher = create_sync_http_fetcher(timeout_seconds=5.0) + return fetcher(f"http://{SCYLLA_HOST}:{SCYLLA_PORT}/localnodes") + + +def _dns_entrypoint_config(fake_url: str) -> object: + parsed = urlsplit(fake_url) + assert parsed.port is not None + return ( + AlternatorConfigBuilder().with_seeds("localhost").with_port(parsed.port).build() + ) + + class TestLiveNodeHelperDiagnostics: """Verify sync helper diagnostics and shared-client lifecycle.""" @@ -49,6 +67,19 @@ def test_helper_exposes_nodes_clients_and_partition_keys(self) -> None: resource = helper.resource() assert "TableNames" in resource.meta.client.list_tables() + def test_helper_discovers_live_cluster_nodes_from_dns_entrypoint( + self, + fake_alternator_server: FakeAlternatorServer, + ) -> None: + fake_alternator_server.set_localnodes(_live_cluster_localnodes()) + + with Helper( + _dns_entrypoint_config(fake_alternator_server.url("/")), + auth=Auth.disabled(), + ) as helper: + assert helper.update_live_nodes() + assert helper.get_nodes() + class TestAsyncLiveNodeHelperDiagnostics: """Verify async helper diagnostics and shared-client lifecycle.""" @@ -73,3 +104,17 @@ async def test_helper_exposes_nodes_clients_and_partition_keys(self) -> None: client = await helper.client() assert "TableNames" in await client.list_tables() + + @pytest.mark.asyncio + async def test_helper_discovers_live_cluster_nodes_from_dns_entrypoint( + self, + fake_alternator_server: FakeAlternatorServer, + ) -> None: + fake_alternator_server.set_localnodes(_live_cluster_localnodes()) + + async with AsyncHelper( + _dns_entrypoint_config(fake_alternator_server.url("/")), + auth=Auth.disabled(), + ) as helper: + assert await helper.update_live_nodes() + assert helper.get_nodes() diff --git a/tests/unit/test_sync_manager.py b/tests/unit/test_sync_manager.py index 7e7473f..e77f93a 100644 --- a/tests/unit/test_sync_manager.py +++ b/tests/unit/test_sync_manager.py @@ -2,13 +2,16 @@ import time from collections.abc import Sequence +from urllib.parse import urlsplit import pytest +from alternator._http import create_sync_http_fetcher from alternator.config import Config from alternator.core.live_nodes import NoNodesAvailableError, SyncLiveNodesManager from alternator.core.routing_scope import ClusterScope, DatacenterScope, RackScope from alternator.exceptions import ConfigurationError +from tests.conftest import FakeAlternatorServer class TestSyncLiveNodesManager: @@ -146,6 +149,29 @@ def mock_fetch(url: str) -> Sequence[str]: assert manager.refresh_nodes() is True assert manager.nodes.nodes == ("dc2-node1", "dc2-node2") + def test_dns_entrypoint_discovers_dns_node_records( + self, + fake_alternator_server: FakeAlternatorServer, + ) -> None: + """Discovery reaches a resolver-backed seed host and keeps DNS node records.""" + fake_alternator_server.set_localnodes(["localhost", "node-a.internal"]) + parsed = urlsplit(fake_alternator_server.url("/")) + assert parsed.port is not None + config = Config( + seed_hosts=["localhost"], + port=parsed.port, + routing_scope=ClusterScope(), + ) + manager = SyncLiveNodesManager( + config, + create_sync_http_fetcher(timeout_seconds=1.0), + ) + + assert manager.refresh_nodes() is True + + assert fake_alternator_server.requested_paths() == ["/localnodes"] + assert manager.nodes.nodes == ("localhost", "node-a.internal") + def test_url_construction(self, config: Config) -> None: """Test URL is constructed correctly.""" captured_url: list[str] = []