Add per-endpoint NPM discovery adapters
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from dns_sync.adapters import (
|
||||
CERTIFICATES,
|
||||
PROXY_HOSTS,
|
||||
Capability,
|
||||
CapabilityOperation,
|
||||
)
|
||||
from dns_sync.config import AppConfig
|
||||
from dns_sync.registry import load_adapter
|
||||
|
||||
|
||||
class NpmAdapterTests(unittest.TestCase):
|
||||
def config(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_ENDPOINTS": "192.168.1.3,192.168.1.4",
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
return AppConfig.from_env()
|
||||
|
||||
def adapter(self, client):
|
||||
config = self.config()
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=client,
|
||||
):
|
||||
return load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
config.npm_endpoints[0],
|
||||
)
|
||||
|
||||
def test_registry_creates_one_adapter_per_endpoint(self):
|
||||
config = self.config()
|
||||
first_client = Mock()
|
||||
second_client = Mock()
|
||||
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
side_effect=[first_client, second_client],
|
||||
) as client_class:
|
||||
adapters = [
|
||||
load_adapter("npm", config, endpoint)
|
||||
for endpoint in config.npm_endpoints
|
||||
]
|
||||
|
||||
self.assertEqual(
|
||||
tuple(adapter.endpoint.identity for adapter in adapters),
|
||||
(
|
||||
"npm://192.168.1.3:81",
|
||||
"npm://192.168.1.4:81",
|
||||
),
|
||||
)
|
||||
self.assertEqual(client_class.call_count, 2)
|
||||
self.assertIs(adapters[0].client, first_client)
|
||||
self.assertIs(adapters[1].client, second_client)
|
||||
|
||||
def test_capabilities_are_discovery_only(self):
|
||||
adapter = self.adapter(Mock())
|
||||
|
||||
self.assertEqual(
|
||||
adapter.capabilities,
|
||||
frozenset(
|
||||
{
|
||||
Capability(
|
||||
PROXY_HOSTS,
|
||||
CapabilityOperation.DISCOVER,
|
||||
),
|
||||
Capability(
|
||||
CERTIFICATES,
|
||||
CapabilityOperation.DISCOVER,
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def test_discovers_proxy_hosts_with_endpoint_local_key(self):
|
||||
client = Mock()
|
||||
client.proxy_hosts.return_value = [
|
||||
{
|
||||
"id": 12,
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "192.168.1.20",
|
||||
"forward_port": 8080,
|
||||
"certificate_id": 67,
|
||||
"enabled": 1,
|
||||
"unexpected_secret": "must-not-be-copied",
|
||||
}
|
||||
]
|
||||
adapter = self.adapter(client)
|
||||
|
||||
discovered = adapter.discover(PROXY_HOSTS)
|
||||
|
||||
self.assertEqual(len(discovered), 1)
|
||||
self.assertEqual(discovered[0].object_type, PROXY_HOSTS)
|
||||
self.assertEqual(discovered[0].local_key, "12")
|
||||
self.assertEqual(
|
||||
discovered[0].payload["domain_names"],
|
||||
["app.example.com"],
|
||||
)
|
||||
self.assertNotIn(
|
||||
"unexpected_secret",
|
||||
discovered[0].payload,
|
||||
)
|
||||
|
||||
def test_certificate_discovery_excludes_secret_material(self):
|
||||
client = Mock()
|
||||
client.certificates.return_value = [
|
||||
{
|
||||
"id": 67,
|
||||
"domain_names": ["app.example.com"],
|
||||
"provider": "other",
|
||||
"nice_name": "cluster copy",
|
||||
"valid_from": "2026-09-01T00:00:00Z",
|
||||
"expires_on": "2026-12-01T00:00:00Z",
|
||||
"private_key": "must-not-be-copied",
|
||||
"certificate": "must-not-be-copied",
|
||||
"meta": {
|
||||
"cluster_managed": True,
|
||||
"acme_renew": True,
|
||||
"dns_challenge": True,
|
||||
"dns_provider_credentials": "must-not-be-copied",
|
||||
},
|
||||
}
|
||||
]
|
||||
adapter = self.adapter(client)
|
||||
|
||||
discovered = adapter.discover(CERTIFICATES)
|
||||
|
||||
self.assertEqual(len(discovered), 1)
|
||||
certificate = discovered[0]
|
||||
self.assertEqual(certificate.local_key, "67")
|
||||
self.assertTrue(certificate.payload["cluster_managed"])
|
||||
self.assertTrue(certificate.payload["acme_renew"])
|
||||
self.assertNotIn("private_key", certificate.payload)
|
||||
self.assertNotIn("certificate", certificate.payload)
|
||||
self.assertNotIn(
|
||||
"dns_provider_credentials",
|
||||
certificate.payload,
|
||||
)
|
||||
|
||||
def test_unsupported_operations_fail_explicitly(self):
|
||||
adapter = self.adapter(Mock())
|
||||
|
||||
with self.assertRaisesRegex(RuntimeError, "cannot discover"):
|
||||
adapter.discover("unsupported_objects")
|
||||
|
||||
with self.assertRaisesRegex(RuntimeError, "does not support apply"):
|
||||
adapter.apply(Mock())
|
||||
|
||||
with self.assertRaisesRegex(RuntimeError, "does not support verify"):
|
||||
adapter.verify(Mock())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user