Add NPM proxy host apply paths
This commit is contained in:
@@ -223,6 +223,21 @@ class NpmClient:
|
||||
"proxy-host update",
|
||||
)
|
||||
|
||||
def delete_proxy_host(
|
||||
self,
|
||||
local_id: str | int,
|
||||
) -> None:
|
||||
proxy_host_id = self._local_id(
|
||||
local_id,
|
||||
"proxy host",
|
||||
)
|
||||
|
||||
self.request(
|
||||
"DELETE",
|
||||
f"/nginx/proxy-hosts/{proxy_host_id}",
|
||||
expected_status=200,
|
||||
)
|
||||
|
||||
def certificate(
|
||||
self,
|
||||
local_id: str | int,
|
||||
|
||||
@@ -315,6 +315,271 @@ class Plugin:
|
||||
|
||||
return baseline
|
||||
|
||||
@staticmethod
|
||||
def _proxy_host_matches(
|
||||
actual: NormalizedObject,
|
||||
desired: NormalizedObject,
|
||||
*,
|
||||
local_key: str | None = None,
|
||||
) -> bool:
|
||||
if actual.object_type != PROXY_HOSTS:
|
||||
return False
|
||||
|
||||
if desired.object_type != PROXY_HOSTS:
|
||||
return False
|
||||
|
||||
if local_key is not None and actual.local_key != local_key:
|
||||
return False
|
||||
|
||||
if actual.logical_key != desired.logical_key:
|
||||
return False
|
||||
|
||||
if actual.payload != desired.payload:
|
||||
return False
|
||||
|
||||
if actual.references != desired.references:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _proxy_host_certificate_id(
|
||||
desired: NormalizedObject,
|
||||
) -> int:
|
||||
certificate_references = []
|
||||
|
||||
for reference in desired.references:
|
||||
if (
|
||||
reference.relation != "certificate"
|
||||
or reference.object_type != CERTIFICATES
|
||||
):
|
||||
raise RuntimeError(
|
||||
"NPM proxy host has unsupported object reference"
|
||||
)
|
||||
|
||||
certificate_references.append(reference)
|
||||
|
||||
if not certificate_references:
|
||||
return 0
|
||||
|
||||
if len(certificate_references) != 1:
|
||||
raise RuntimeError(
|
||||
"NPM proxy host requires at most one "
|
||||
"certificate reference"
|
||||
)
|
||||
|
||||
reference = certificate_references[0]
|
||||
|
||||
if reference.local_key is None:
|
||||
raise RuntimeError(
|
||||
"NPM proxy host certificate reference "
|
||||
"was not resolved to an endpoint-local key"
|
||||
)
|
||||
|
||||
try:
|
||||
certificate_id = int(reference.local_key)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise RuntimeError(
|
||||
"NPM proxy host certificate reference "
|
||||
"has invalid endpoint-local key"
|
||||
) from exc
|
||||
|
||||
if certificate_id < 1:
|
||||
raise RuntimeError(
|
||||
"NPM proxy host certificate reference "
|
||||
"has invalid endpoint-local key"
|
||||
)
|
||||
|
||||
return certificate_id
|
||||
|
||||
def _proxy_host_api_payload(
|
||||
self,
|
||||
desired: NormalizedObject,
|
||||
) -> dict[str, Any]:
|
||||
if desired.object_type != PROXY_HOSTS:
|
||||
raise RuntimeError(
|
||||
"Proxy-host API payload requires proxy-host state"
|
||||
)
|
||||
|
||||
payload = desired.payload
|
||||
|
||||
domains = payload.get("domain_names")
|
||||
if (
|
||||
not isinstance(domains, list)
|
||||
or not domains
|
||||
or not all(
|
||||
isinstance(value, str) and value
|
||||
for value in domains
|
||||
)
|
||||
):
|
||||
raise RuntimeError(
|
||||
"NPM proxy host requires domain_names"
|
||||
)
|
||||
|
||||
forward_scheme = payload.get("forward_scheme")
|
||||
if (
|
||||
not isinstance(forward_scheme, str)
|
||||
or not forward_scheme
|
||||
):
|
||||
raise RuntimeError(
|
||||
"NPM proxy host requires forward_scheme"
|
||||
)
|
||||
|
||||
forward_host = payload.get("forward_host")
|
||||
if (
|
||||
not isinstance(forward_host, str)
|
||||
or not forward_host
|
||||
):
|
||||
raise RuntimeError(
|
||||
"NPM proxy host requires forward_host"
|
||||
)
|
||||
|
||||
forward_port = payload.get("forward_port")
|
||||
if (
|
||||
isinstance(forward_port, bool)
|
||||
or not isinstance(forward_port, int)
|
||||
or forward_port < 1
|
||||
or forward_port > 65535
|
||||
):
|
||||
raise RuntimeError(
|
||||
"NPM proxy host requires valid forward_port"
|
||||
)
|
||||
|
||||
api_payload: dict[str, Any] = {
|
||||
"domain_names": list(domains),
|
||||
"forward_scheme": forward_scheme,
|
||||
"forward_host": forward_host,
|
||||
"forward_port": forward_port,
|
||||
"certificate_id": (
|
||||
self._proxy_host_certificate_id(desired)
|
||||
),
|
||||
}
|
||||
|
||||
for field in _PROXY_HOST_FIELDS:
|
||||
if field in (
|
||||
"domain_names",
|
||||
"forward_scheme",
|
||||
"forward_host",
|
||||
"forward_port",
|
||||
"certificate_id",
|
||||
):
|
||||
continue
|
||||
|
||||
if field in payload:
|
||||
api_payload[field] = payload[field]
|
||||
|
||||
return api_payload
|
||||
|
||||
def _create_proxy_host(
|
||||
self,
|
||||
desired: NormalizedObject,
|
||||
) -> ApplyResult:
|
||||
payload = self._proxy_host_api_payload(
|
||||
desired
|
||||
)
|
||||
|
||||
created = self.client.create_proxy_host(
|
||||
payload
|
||||
)
|
||||
|
||||
local_key = self._local_key(
|
||||
created,
|
||||
"proxy host",
|
||||
)
|
||||
|
||||
try:
|
||||
row = self.client.proxy_host(local_key)
|
||||
|
||||
actual = normalize_discovered_object(
|
||||
self._proxy_host_from_row(row)
|
||||
)
|
||||
|
||||
if not self._proxy_host_matches(
|
||||
actual,
|
||||
desired,
|
||||
local_key=local_key,
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Created NPM proxy host does not match "
|
||||
"desired state"
|
||||
)
|
||||
|
||||
except Exception:
|
||||
try:
|
||||
self.client.delete_proxy_host(
|
||||
local_key
|
||||
)
|
||||
except Exception as rollback_error:
|
||||
raise RuntimeError(
|
||||
"NPM proxy-host creation failed and "
|
||||
"rollback also failed"
|
||||
) from rollback_error
|
||||
|
||||
raise
|
||||
|
||||
return ApplyResult(
|
||||
object=actual,
|
||||
detail="created proxy host",
|
||||
)
|
||||
|
||||
def _update_proxy_host(
|
||||
self,
|
||||
action: PlanAction,
|
||||
) -> ApplyResult:
|
||||
desired = action.desired
|
||||
current = action.current
|
||||
|
||||
if desired is None:
|
||||
raise RuntimeError(
|
||||
"NPM proxy-host update requires desired state"
|
||||
)
|
||||
|
||||
if (
|
||||
current is None
|
||||
or current.object_type != PROXY_HOSTS
|
||||
or current.local_key is None
|
||||
):
|
||||
raise RuntimeError(
|
||||
"NPM proxy-host update requires "
|
||||
"current endpoint-local state"
|
||||
)
|
||||
|
||||
if current.logical_key != desired.logical_key:
|
||||
raise RuntimeError(
|
||||
"NPM proxy-host update identity changed"
|
||||
)
|
||||
|
||||
local_key = current.local_key
|
||||
payload = self._proxy_host_api_payload(
|
||||
desired
|
||||
)
|
||||
|
||||
self.client.update_proxy_host(
|
||||
local_key,
|
||||
payload,
|
||||
)
|
||||
|
||||
row = self.client.proxy_host(local_key)
|
||||
|
||||
actual = normalize_discovered_object(
|
||||
self._proxy_host_from_row(row)
|
||||
)
|
||||
|
||||
if not self._proxy_host_matches(
|
||||
actual,
|
||||
desired,
|
||||
local_key=local_key,
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Updated NPM proxy host does not match "
|
||||
"desired state"
|
||||
)
|
||||
|
||||
return ApplyResult(
|
||||
object=actual,
|
||||
detail="updated proxy host",
|
||||
)
|
||||
|
||||
def apply(
|
||||
self,
|
||||
action: PlanAction,
|
||||
@@ -337,6 +602,18 @@ class Plugin:
|
||||
):
|
||||
return self._create_certificate(desired)
|
||||
|
||||
if (
|
||||
desired.object_type == PROXY_HOSTS
|
||||
and action.operation == PlanOperation.CREATE
|
||||
):
|
||||
return self._create_proxy_host(desired)
|
||||
|
||||
if (
|
||||
desired.object_type == PROXY_HOSTS
|
||||
and action.operation == PlanOperation.UPDATE
|
||||
):
|
||||
return self._update_proxy_host(action)
|
||||
|
||||
raise RuntimeError(
|
||||
f"{self.endpoint.identity} does not support "
|
||||
f"{action.operation} for {desired.object_type}"
|
||||
@@ -465,7 +742,11 @@ class Plugin:
|
||||
self._proxy_host_from_row(row)
|
||||
)
|
||||
|
||||
return actual == object
|
||||
return self._proxy_host_matches(
|
||||
actual,
|
||||
object,
|
||||
local_key=object.local_key,
|
||||
)
|
||||
|
||||
if object.object_type == CERTIFICATES:
|
||||
row = self.client.certificate(
|
||||
|
||||
@@ -1161,6 +1161,444 @@ class NpmAdapterTests(unittest.TestCase):
|
||||
target_client.upload_certificate.assert_not_called()
|
||||
target_client.delete_certificate.assert_not_called()
|
||||
|
||||
def test_proxy_host_create_uses_resolved_target_certificate_id(self):
|
||||
client = Mock()
|
||||
|
||||
client.create_proxy_host.return_value = {
|
||||
"id": 120,
|
||||
}
|
||||
|
||||
client.proxy_host.return_value = {
|
||||
"id": 120,
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "backend.internal",
|
||||
"forward_port": 8080,
|
||||
"certificate_id": 84,
|
||||
"ssl_forced": True,
|
||||
"caching_enabled": False,
|
||||
"block_exploits": True,
|
||||
"allow_websocket_upgrade": True,
|
||||
"http2_support": True,
|
||||
"hsts_enabled": True,
|
||||
"hsts_subdomains": False,
|
||||
"enabled": True,
|
||||
}
|
||||
|
||||
adapter = self.adapter(client)
|
||||
|
||||
desired = NormalizedObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
logical_key="app.example.com",
|
||||
payload={
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "backend.internal",
|
||||
"forward_port": 8080,
|
||||
"ssl_forced": True,
|
||||
"caching_enabled": False,
|
||||
"block_exploits": True,
|
||||
"allow_websocket_upgrade": True,
|
||||
"http2_support": True,
|
||||
"hsts_enabled": True,
|
||||
"hsts_subdomains": False,
|
||||
"enabled": True,
|
||||
},
|
||||
references=(
|
||||
ObjectReference(
|
||||
relation="certificate",
|
||||
object_type=CERTIFICATES,
|
||||
local_key="84",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
result = adapter.apply(
|
||||
PlanAction(
|
||||
endpoint_id=adapter.endpoint.identity,
|
||||
operation=PlanOperation.CREATE,
|
||||
desired=desired,
|
||||
)
|
||||
)
|
||||
|
||||
client.create_proxy_host.assert_called_once_with(
|
||||
{
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "backend.internal",
|
||||
"forward_port": 8080,
|
||||
"certificate_id": 84,
|
||||
"ssl_forced": True,
|
||||
"caching_enabled": False,
|
||||
"block_exploits": True,
|
||||
"allow_websocket_upgrade": True,
|
||||
"http2_support": True,
|
||||
"hsts_enabled": True,
|
||||
"hsts_subdomains": False,
|
||||
"enabled": True,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
result.object.local_key,
|
||||
"120",
|
||||
)
|
||||
self.assertEqual(
|
||||
result.object.references,
|
||||
desired.references,
|
||||
)
|
||||
client.delete_proxy_host.assert_not_called()
|
||||
|
||||
def test_proxy_host_create_without_certificate_uses_zero(self):
|
||||
client = Mock()
|
||||
|
||||
client.create_proxy_host.return_value = {
|
||||
"id": 120,
|
||||
}
|
||||
|
||||
client.proxy_host.return_value = {
|
||||
"id": 120,
|
||||
"domain_names": ["plain.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "backend.internal",
|
||||
"forward_port": 8080,
|
||||
"certificate_id": 0,
|
||||
"enabled": True,
|
||||
}
|
||||
|
||||
adapter = self.adapter(client)
|
||||
|
||||
desired = normalize_discovered_object(
|
||||
adapter._proxy_host_from_row(
|
||||
client.proxy_host.return_value
|
||||
)
|
||||
)
|
||||
|
||||
desired = NormalizedObject(
|
||||
object_type=desired.object_type,
|
||||
logical_key=desired.logical_key,
|
||||
payload=desired.payload,
|
||||
references=desired.references,
|
||||
)
|
||||
|
||||
result = adapter.apply(
|
||||
PlanAction(
|
||||
endpoint_id=adapter.endpoint.identity,
|
||||
operation=PlanOperation.CREATE,
|
||||
desired=desired,
|
||||
)
|
||||
)
|
||||
|
||||
payload = client.create_proxy_host.call_args.kwargs
|
||||
if not payload:
|
||||
payload = client.create_proxy_host.call_args.args[0]
|
||||
|
||||
self.assertEqual(
|
||||
payload["certificate_id"],
|
||||
0,
|
||||
)
|
||||
self.assertEqual(
|
||||
result.object.references,
|
||||
(),
|
||||
)
|
||||
|
||||
def test_proxy_host_create_rolls_back_when_refetch_mismatches(self):
|
||||
client = Mock()
|
||||
|
||||
client.create_proxy_host.return_value = {
|
||||
"id": 120,
|
||||
}
|
||||
|
||||
client.proxy_host.return_value = {
|
||||
"id": 120,
|
||||
"domain_names": ["wrong.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "backend.internal",
|
||||
"forward_port": 8080,
|
||||
"certificate_id": 0,
|
||||
"enabled": True,
|
||||
}
|
||||
|
||||
adapter = self.adapter(client)
|
||||
|
||||
desired = NormalizedObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
logical_key="app.example.com",
|
||||
payload={
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "backend.internal",
|
||||
"forward_port": 8080,
|
||||
"ssl_forced": False,
|
||||
"caching_enabled": False,
|
||||
"block_exploits": False,
|
||||
"allow_websocket_upgrade": False,
|
||||
"http2_support": False,
|
||||
"hsts_enabled": False,
|
||||
"hsts_subdomains": False,
|
||||
"enabled": True,
|
||||
},
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"does not match desired",
|
||||
):
|
||||
adapter.apply(
|
||||
PlanAction(
|
||||
endpoint_id=adapter.endpoint.identity,
|
||||
operation=PlanOperation.CREATE,
|
||||
desired=desired,
|
||||
)
|
||||
)
|
||||
|
||||
client.delete_proxy_host.assert_called_once_with(
|
||||
"120"
|
||||
)
|
||||
|
||||
def test_proxy_host_create_reports_rollback_failure(self):
|
||||
client = Mock()
|
||||
|
||||
client.create_proxy_host.return_value = {
|
||||
"id": 120,
|
||||
}
|
||||
client.proxy_host.side_effect = RuntimeError(
|
||||
"refetch failed"
|
||||
)
|
||||
client.delete_proxy_host.side_effect = RuntimeError(
|
||||
"delete failed"
|
||||
)
|
||||
|
||||
adapter = self.adapter(client)
|
||||
|
||||
desired = NormalizedObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
logical_key="app.example.com",
|
||||
payload={
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "backend.internal",
|
||||
"forward_port": 8080,
|
||||
},
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"rollback also failed",
|
||||
):
|
||||
adapter.apply(
|
||||
PlanAction(
|
||||
endpoint_id=adapter.endpoint.identity,
|
||||
operation=PlanOperation.CREATE,
|
||||
desired=desired,
|
||||
)
|
||||
)
|
||||
|
||||
def test_proxy_host_update_uses_current_local_id_and_resolved_reference(self):
|
||||
client = Mock()
|
||||
|
||||
client.proxy_host.return_value = {
|
||||
"id": 12,
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "https",
|
||||
"forward_host": "new-backend.internal",
|
||||
"forward_port": 8443,
|
||||
"certificate_id": 84,
|
||||
"ssl_forced": True,
|
||||
"enabled": True,
|
||||
}
|
||||
|
||||
adapter = self.adapter(client)
|
||||
|
||||
current = NormalizedObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
logical_key="app.example.com",
|
||||
local_key="12",
|
||||
payload={
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "old-backend.internal",
|
||||
"forward_port": 8080,
|
||||
"ssl_forced": False,
|
||||
"caching_enabled": False,
|
||||
"block_exploits": False,
|
||||
"allow_websocket_upgrade": False,
|
||||
"http2_support": False,
|
||||
"hsts_enabled": False,
|
||||
"hsts_subdomains": False,
|
||||
"enabled": True,
|
||||
},
|
||||
)
|
||||
|
||||
actual = normalize_discovered_object(
|
||||
adapter._proxy_host_from_row(
|
||||
client.proxy_host.return_value
|
||||
)
|
||||
)
|
||||
|
||||
desired = NormalizedObject(
|
||||
object_type=actual.object_type,
|
||||
logical_key=actual.logical_key,
|
||||
payload=actual.payload,
|
||||
references=actual.references,
|
||||
)
|
||||
|
||||
result = adapter.apply(
|
||||
PlanAction(
|
||||
endpoint_id=adapter.endpoint.identity,
|
||||
operation=PlanOperation.UPDATE,
|
||||
desired=desired,
|
||||
current=current,
|
||||
)
|
||||
)
|
||||
|
||||
client.update_proxy_host.assert_called_once()
|
||||
|
||||
args = client.update_proxy_host.call_args.args
|
||||
self.assertEqual(args[0], "12")
|
||||
self.assertEqual(
|
||||
args[1]["certificate_id"],
|
||||
84,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
result.object.local_key,
|
||||
"12",
|
||||
)
|
||||
self.assertEqual(
|
||||
result.object.references,
|
||||
desired.references,
|
||||
)
|
||||
client.delete_proxy_host.assert_not_called()
|
||||
|
||||
def test_proxy_host_update_fails_when_target_does_not_converge(self):
|
||||
client = Mock()
|
||||
|
||||
client.proxy_host.return_value = {
|
||||
"id": 12,
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "still-old.internal",
|
||||
"forward_port": 8080,
|
||||
"certificate_id": 0,
|
||||
"enabled": True,
|
||||
}
|
||||
|
||||
adapter = self.adapter(client)
|
||||
|
||||
current = normalize_discovered_object(
|
||||
adapter._proxy_host_from_row(
|
||||
client.proxy_host.return_value
|
||||
)
|
||||
)
|
||||
|
||||
desired = NormalizedObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
logical_key="app.example.com",
|
||||
payload={
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "new.internal",
|
||||
"forward_port": 8080,
|
||||
"ssl_forced": False,
|
||||
"caching_enabled": False,
|
||||
"block_exploits": False,
|
||||
"allow_websocket_upgrade": False,
|
||||
"http2_support": False,
|
||||
"hsts_enabled": False,
|
||||
"hsts_subdomains": False,
|
||||
"enabled": True,
|
||||
},
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"does not match desired",
|
||||
):
|
||||
adapter.apply(
|
||||
PlanAction(
|
||||
endpoint_id=adapter.endpoint.identity,
|
||||
operation=PlanOperation.UPDATE,
|
||||
desired=desired,
|
||||
current=current,
|
||||
)
|
||||
)
|
||||
|
||||
client.delete_proxy_host.assert_not_called()
|
||||
|
||||
def test_proxy_host_apply_rejects_unresolved_certificate_reference(self):
|
||||
client = Mock()
|
||||
adapter = self.adapter(client)
|
||||
|
||||
desired = NormalizedObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
logical_key="app.example.com",
|
||||
payload={
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "backend.internal",
|
||||
"forward_port": 8080,
|
||||
},
|
||||
references=(
|
||||
ObjectReference(
|
||||
relation="certificate",
|
||||
object_type=CERTIFICATES,
|
||||
logical_key="app.example.com",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"not resolved",
|
||||
):
|
||||
adapter.apply(
|
||||
PlanAction(
|
||||
endpoint_id=adapter.endpoint.identity,
|
||||
operation=PlanOperation.CREATE,
|
||||
desired=desired,
|
||||
)
|
||||
)
|
||||
|
||||
client.create_proxy_host.assert_not_called()
|
||||
client.update_proxy_host.assert_not_called()
|
||||
|
||||
def test_proxy_host_apply_rejects_unsupported_reference(self):
|
||||
client = Mock()
|
||||
adapter = self.adapter(client)
|
||||
|
||||
desired = NormalizedObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
logical_key="app.example.com",
|
||||
payload={
|
||||
"domain_names": ["app.example.com"],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "backend.internal",
|
||||
"forward_port": 8080,
|
||||
},
|
||||
references=(
|
||||
ObjectReference(
|
||||
relation="other",
|
||||
object_type=CERTIFICATES,
|
||||
local_key="84",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"unsupported object reference",
|
||||
):
|
||||
adapter.apply(
|
||||
PlanAction(
|
||||
endpoint_id=adapter.endpoint.identity,
|
||||
operation=PlanOperation.CREATE,
|
||||
desired=desired,
|
||||
)
|
||||
)
|
||||
|
||||
client.create_proxy_host.assert_not_called()
|
||||
|
||||
def test_unsupported_operations_fail_explicitly(self):
|
||||
adapter = self.adapter(Mock())
|
||||
|
||||
|
||||
@@ -186,6 +186,19 @@ class NpmClientMutationTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(result["id"], 12)
|
||||
|
||||
def test_deletes_proxy_host_by_endpoint_local_id(self):
|
||||
client = self.client()
|
||||
client.request = Mock()
|
||||
|
||||
result = client.delete_proxy_host("12")
|
||||
|
||||
self.assertIsNone(result)
|
||||
client.request.assert_called_once_with(
|
||||
"DELETE",
|
||||
"/nginx/proxy-hosts/12",
|
||||
expected_status=200,
|
||||
)
|
||||
|
||||
def test_gets_one_certificate_by_endpoint_local_id(self):
|
||||
client = self.client()
|
||||
client.get_json = Mock(
|
||||
|
||||
Reference in New Issue
Block a user