From 79342590ba37c6a168494aecced3754040536939 Mon Sep 17 00:00:00 2001 From: Lyncoln Date: Sat, 19 Sep 2026 16:58:15 -0400 Subject: [PATCH] Bind NPM clients to configured endpoints --- dns_sync/npm_client.py | 17 +++++++-- tests/test_npm_client.py | 75 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 tests/test_npm_client.py diff --git a/dns_sync/npm_client.py b/dns_sync/npm_client.py index 2eb812f..54d5cb4 100644 --- a/dns_sync/npm_client.py +++ b/dns_sync/npm_client.py @@ -4,12 +4,22 @@ from typing import Any import requests import urllib3 +from .adapters import Endpoint + class NpmClient: """Authenticated client for the Nginx Proxy Manager API.""" - def __init__(self, config): + def __init__( + self, + config, + endpoint: Endpoint | None = None, + ): self.config = config + self.endpoint = endpoint or config.npm_endpoints[0] + + if self.endpoint.plugin != "npm": + raise RuntimeError("NpmClient requires an npm endpoint") self.identity = os.getenv("NPM_IDENTITY") self.secret = os.getenv("NPM_SECRET") @@ -30,7 +40,10 @@ class NpmClient: @property def api_url(self) -> str: - return f"{self.config.proxy_url}/api" + return ( + f"{self.config.proxy_api_scheme}://" + f"{self.endpoint.address}/api" + ) def login(self) -> None: response = self.session.post( diff --git a/tests/test_npm_client.py b/tests/test_npm_client.py new file mode 100644 index 0000000..785896a --- /dev/null +++ b/tests/test_npm_client.py @@ -0,0 +1,75 @@ +import os +import unittest +from unittest.mock import patch + +from dns_sync.adapters import Endpoint +from dns_sync.config import AppConfig +from dns_sync.npm_client import NpmClient + + +class NpmClientEndpointTests(unittest.TestCase): + def config(self): + return AppConfig.from_env() + + def test_default_client_uses_first_configured_endpoint(self): + with patch.dict( + os.environ, + { + "NPM_ENDPOINTS": "192.168.1.3,192.168.1.4:8081", + "DNS_TARGET_IP": "192.168.1.101", + "NPM_IDENTITY": "test@example.com", + "NPM_SECRET": "not-a-real-secret", + }, + clear=True, + ): + config = self.config() + with patch( + "dns_sync.npm_client.requests.Session.request" + ) as request: + client = NpmClient(config) + + request.assert_not_called() + self.assertEqual(client.api_url, "http://192.168.1.3:81/api") + + def test_client_can_select_specific_configured_endpoint(self): + with patch.dict( + os.environ, + { + "NPM_ENDPOINTS": "192.168.1.3,192.168.1.4:8081", + "DNS_TARGET_IP": "192.168.1.101", + "NPM_IDENTITY": "test@example.com", + "NPM_SECRET": "not-a-real-secret", + }, + clear=True, + ): + config = self.config() + client = NpmClient(config, config.npm_endpoints[1]) + + self.assertEqual( + client.api_url, + "http://192.168.1.4:8081/api", + ) + self.assertEqual( + client.endpoint.identity, + "npm://192.168.1.4:8081", + ) + + def test_client_rejects_non_npm_endpoint(self): + with patch.dict( + os.environ, + { + "PROXY_IP": "192.168.1.101", + "NPM_IDENTITY": "test@example.com", + "NPM_SECRET": "not-a-real-secret", + }, + clear=True, + ): + config = self.config() + endpoint = Endpoint("verizon", "192.168.1.1") + + with self.assertRaisesRegex(RuntimeError, "npm endpoint"): + NpmClient(config, endpoint) + + +if __name__ == "__main__": + unittest.main()