feat(egress-proxy): retarget remediation at egress-proxy (PRD 0017 chunk 3)
test / unit (pull_request) Successful in 19s
test / integration (pull_request) Successful in 1m6s

Finishes PRD 0017. The `cred-proxy-block` MCP tool is renamed and
its remediation apply path is repointed at egress-proxy.

  - `claude_bottle/supervise.py` — `TOOL_CRED_PROXY_BLOCK` →
    `TOOL_EGRESS_PROXY_BLOCK`; `COMPONENT_FOR_TOOL` maps the new
    tool ID to `egress-proxy` for audit-log routing.

  - `claude_bottle/supervise_server.py` — tool definition renamed
    + description rewritten: "Call when egress-proxy refused your
    HTTPS request ... Read the current routes.yaml from /etc/
    claude-bottle/current-config/routes.yaml, compose a modified
    version, pass the full new file plus a justification." The
    syntactic validator dispatches on the new tool ID.

  - `claude_bottle/backend/docker/egress_proxy_apply.py` — renamed
    from `cred_proxy_apply.py`. Reads routes.yaml from
    /etc/egress-proxy/routes.yaml via `docker exec cat`; validates
    via `egress_proxy_addon_core.load_routes` (so both sides use
    the same parser); writes via `docker cp`; SIGHUPs egress-proxy
    with `docker kill --signal HUP`. `EgressProxyApplyError`
    replaces `CredProxyApplyError`.

  - `claude_bottle/cli/dashboard.py` — wires the new apply +
    `discover_egress_proxy_slugs` helper; the operator-initiated
    `routes edit <bottle>` verb now writes to egress-proxy with
    `.yaml` suffix. Stale follow-up comment about path-aware
    filtering removed — PRD 0017 settled that question.

  - `tests/integration/test_supervise_sidecar.py` — restores the
    approval round-trip test (chunk 2 had switched it to a reject
    path because no cred-proxy existed). Approval stubs
    `apply_routes_change` so the test focuses on the supervise
    queue/response plumbing rather than docker-exec into a real
    egress-proxy sidecar (that's covered separately).

  - `tests/unit/test_egress_proxy_apply.py` — rewritten against
    the new validator; covers JSON shape, missing routes key,
    partial-auth-pair rejection (the addon-core parser catches
    these before SIGHUP).

  - PRDs 0010 + 0014 — status headers updated to
    Superseded / Retargeted with a callout block pointing at PRD
    0017's migration section. Historical text preserved.

384 unit + integration tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 15:13:44 -04:00
parent a135415dfe
commit 9cd583fbbb
14 changed files with 361 additions and 333 deletions
-39
View File
@@ -1,39 +0,0 @@
"""Unit: validate_routes_json (PRD 0014 Phase 2). docker exec / cp /
kill paths are covered by the integration test."""
import unittest
from claude_bottle.backend.docker.cred_proxy_apply import (
CredProxyApplyError,
validate_routes_json,
)
class TestValidateRoutesJson(unittest.TestCase):
def test_accepts_routes_array(self):
validate_routes_json('{"routes": []}')
validate_routes_json(
'{"routes": [{"path": "/a/", "upstream": "https://example.com",'
' "auth_scheme": "Bearer", "token_env": "T0"}]}'
)
def test_rejects_bad_json(self):
with self.assertRaises(CredProxyApplyError) as cm:
validate_routes_json("{not json")
self.assertIn("not valid JSON", str(cm.exception))
def test_rejects_non_object_top_level(self):
with self.assertRaises(CredProxyApplyError):
validate_routes_json("[]")
def test_rejects_missing_routes_key(self):
with self.assertRaises(CredProxyApplyError):
validate_routes_json('{"other": []}')
def test_rejects_non_list_routes(self):
with self.assertRaises(CredProxyApplyError):
validate_routes_json('{"routes": "not a list"}')
if __name__ == "__main__":
unittest.main()
+35 -35
View File
@@ -17,7 +17,7 @@ from pathlib import Path
from claude_bottle import supervise
from claude_bottle.backend.docker.capability_apply import CapabilityApplyError
from claude_bottle.backend.docker.cred_proxy_apply import CredProxyApplyError
from claude_bottle.backend.docker.egress_proxy_apply import EgressProxyApplyError
from claude_bottle.backend.docker.pipelock_apply import PipelockApplyError
from claude_bottle.cli import dashboard
from claude_bottle.supervise import (
@@ -26,7 +26,7 @@ from claude_bottle.supervise import (
STATUS_MODIFIED,
STATUS_REJECTED,
TOOL_CAPABILITY_BLOCK,
TOOL_CRED_PROXY_BLOCK,
TOOL_EGRESS_PROXY_BLOCK,
TOOL_PIPELOCK_BLOCK,
read_audit_entries,
read_response,
@@ -37,13 +37,13 @@ from claude_bottle.supervise import (
FIXED = datetime(2026, 5, 25, 12, 0, 0, tzinfo=timezone.utc)
def _proposal(slug: str = "dev", tool: str = TOOL_CRED_PROXY_BLOCK) -> Proposal:
def _proposal(slug: str = "dev", tool: str = TOOL_EGRESS_PROXY_BLOCK) -> Proposal:
# Per-tool payload shape: cred-proxy gets routes.json, pipelock
# gets a failed URL (PR #25 follow-up), capability gets a
# Dockerfile-ish blob. Match the production dispatch in
# PROPOSED_FILE_FIELD.
payloads = {
TOOL_CRED_PROXY_BLOCK: '{"routes": []}\n',
TOOL_EGRESS_PROXY_BLOCK: '{"routes": []}\n',
TOOL_PIPELOCK_BLOCK: "https://example.com/path",
TOOL_CAPABILITY_BLOCK: "FROM python:3.13\n",
}
@@ -95,13 +95,13 @@ class TestDiscoverPending(_FakeHomeMixin, unittest.TestCase):
def test_sorted_by_arrival_across_bottles(self):
early = Proposal.new(
bottle_slug="api", tool=TOOL_CRED_PROXY_BLOCK,
bottle_slug="api", tool=TOOL_EGRESS_PROXY_BLOCK,
proposed_file="{}", justification="early",
current_file_hash="h",
now=datetime(2026, 5, 25, 10, 0, 0, tzinfo=timezone.utc),
)
late = Proposal.new(
bottle_slug="dev", tool=TOOL_CRED_PROXY_BLOCK,
bottle_slug="dev", tool=TOOL_EGRESS_PROXY_BLOCK,
proposed_file="{}", justification="late",
current_file_hash="h",
now=datetime(2026, 5, 25, 14, 0, 0, tzinfo=timezone.utc),
@@ -151,7 +151,7 @@ class TestApproveReject(_FakeHomeMixin, unittest.TestCase):
dashboard.apply_capability_change = self._original_apply_capability
self._teardown_fake_home()
def _enqueue(self, tool: str = TOOL_CRED_PROXY_BLOCK):
def _enqueue(self, tool: str = TOOL_EGRESS_PROXY_BLOCK):
p = _proposal(tool=tool)
qdir = supervise.queue_dir_for_slug("dev")
qdir.mkdir(parents=True, exist_ok=True)
@@ -164,7 +164,7 @@ class TestApproveReject(_FakeHomeMixin, unittest.TestCase):
resp = read_response(qp.queue_dir, qp.proposal.id)
self.assertEqual(STATUS_APPROVED, resp.status)
self.assertIsNone(resp.final_file)
entries = read_audit_entries("cred-proxy", "dev")
entries = read_audit_entries("egress-proxy", "dev")
self.assertEqual(1, len(entries))
self.assertEqual("approved", entries[0].operator_action)
@@ -175,7 +175,7 @@ class TestApproveReject(_FakeHomeMixin, unittest.TestCase):
self.assertEqual(STATUS_MODIFIED, resp.status)
self.assertEqual('{"routes": [{"path": "/x/"}]}\n', resp.final_file)
self.assertEqual("tweaked", resp.notes)
entries = read_audit_entries("cred-proxy", "dev")
entries = read_audit_entries("egress-proxy", "dev")
self.assertEqual("modified", entries[0].operator_action)
def test_reject_writes_rejection(self):
@@ -184,7 +184,7 @@ class TestApproveReject(_FakeHomeMixin, unittest.TestCase):
resp = read_response(qp.queue_dir, qp.proposal.id)
self.assertEqual(STATUS_REJECTED, resp.status)
self.assertEqual("nope", resp.notes)
entries = read_audit_entries("cred-proxy", "dev")
entries = read_audit_entries("egress-proxy", "dev")
self.assertEqual("rejected", entries[0].operator_action)
self.assertEqual("nope", entries[0].operator_notes)
@@ -193,18 +193,18 @@ class TestApproveReject(_FakeHomeMixin, unittest.TestCase):
dashboard.approve(qp)
# No audit log for capability-block (per PRD 0013 / 0016).
# cred-proxy and pipelock logs both empty.
self.assertEqual([], read_audit_entries("cred-proxy", "dev"))
self.assertEqual([], read_audit_entries("egress-proxy", "dev"))
self.assertEqual([], read_audit_entries("pipelock", "dev"))
def test_pipelock_audit_distinct_from_cred_proxy(self):
def test_pipelock_audit_distinct_from_egress_proxy(self):
qp = self._enqueue(tool=TOOL_PIPELOCK_BLOCK)
dashboard.approve(qp)
self.assertEqual(1, len(read_audit_entries("pipelock", "dev")))
self.assertEqual(0, len(read_audit_entries("cred-proxy", "dev")))
self.assertEqual(0, len(read_audit_entries("egress-proxy", "dev")))
class TestCredProxyApplyWiring(_FakeHomeMixin, unittest.TestCase):
"""PRD 0014 Phase 3: approve() on a cred-proxy-block proposal
class TestEgressProxyApplyWiring(_FakeHomeMixin, unittest.TestCase):
"""PRD 0014 Phase 3: approve() on a egress-proxy-block proposal
must call apply_routes_change with the right args and surface
its failures."""
@@ -216,9 +216,9 @@ class TestCredProxyApplyWiring(_FakeHomeMixin, unittest.TestCase):
dashboard.apply_routes_change = self._original_apply
self._teardown_fake_home()
def _enqueue_cred_proxy(self, proposed: str = '{"routes": []}\n'):
def _enqueue_egress_proxy(self, proposed: str = '{"routes": []}\n'):
p = Proposal.new(
bottle_slug="dev", tool=TOOL_CRED_PROXY_BLOCK,
bottle_slug="dev", tool=TOOL_EGRESS_PROXY_BLOCK,
proposed_file=proposed,
justification="need a route",
current_file_hash=sha256_hex(proposed),
@@ -229,12 +229,12 @@ class TestCredProxyApplyWiring(_FakeHomeMixin, unittest.TestCase):
supervise.write_proposal(qdir, p)
return dashboard.QueuedProposal(proposal=p, queue_dir=qdir)
def test_cred_proxy_block_calls_apply_with_proposed_file(self):
def test_egress_proxy_block_calls_apply_with_proposed_file(self):
calls = []
dashboard.apply_routes_change = lambda slug, content: (
calls.append((slug, content)) or ("before", content)
)
qp = self._enqueue_cred_proxy(proposed='{"routes": [{"path": "/new/"}]}\n')
qp = self._enqueue_egress_proxy(proposed='{"routes": [{"path": "/new/"}]}\n')
dashboard.approve(qp)
self.assertEqual(1, len(calls))
slug, content = calls[0]
@@ -246,16 +246,16 @@ class TestCredProxyApplyWiring(_FakeHomeMixin, unittest.TestCase):
dashboard.apply_routes_change = lambda slug, content: (
calls.append(content) or ("before", content)
)
qp = self._enqueue_cred_proxy()
qp = self._enqueue_egress_proxy()
dashboard.approve(qp, final_file='{"routes": [{"path": "/edited/"}]}\n', notes="tweaked")
self.assertEqual(['{"routes": [{"path": "/edited/"}]}\n'], calls)
def test_apply_failure_blocks_response_and_audit(self):
dashboard.apply_routes_change = lambda slug, content: (_ for _ in ()).throw(
CredProxyApplyError("docker exec failed")
EgressProxyApplyError("docker exec failed")
)
qp = self._enqueue_cred_proxy()
with self.assertRaises(CredProxyApplyError):
qp = self._enqueue_egress_proxy()
with self.assertRaises(EgressProxyApplyError):
dashboard.approve(qp)
# No response file (proposal stays pending).
self.assertEqual(
@@ -263,16 +263,16 @@ class TestCredProxyApplyWiring(_FakeHomeMixin, unittest.TestCase):
[p.id for p in supervise.list_pending_proposals(qp.queue_dir)],
)
# No audit entry.
self.assertEqual([], read_audit_entries("cred-proxy", "dev"))
self.assertEqual([], read_audit_entries("egress-proxy", "dev"))
def test_real_diff_lands_in_audit(self):
dashboard.apply_routes_change = lambda slug, content: (
'{"routes": []}\n', # before
'{"routes": [{"path": "/new/"}]}\n', # after
)
qp = self._enqueue_cred_proxy(proposed='{"routes": [{"path": "/new/"}]}\n')
qp = self._enqueue_egress_proxy(proposed='{"routes": [{"path": "/new/"}]}\n')
dashboard.approve(qp)
entries = read_audit_entries("cred-proxy", "dev")
entries = read_audit_entries("egress-proxy", "dev")
self.assertEqual(1, len(entries))
self.assertIn('+{"routes": [{"path": "/new/"}]}', entries[0].diff)
self.assertIn('-{"routes": []}', entries[0].diff)
@@ -282,13 +282,13 @@ class TestCredProxyApplyWiring(_FakeHomeMixin, unittest.TestCase):
dashboard.apply_routes_change = lambda slug, content: (
called.append(True) or ("", content)
)
qp = self._enqueue_cred_proxy()
qp = self._enqueue_egress_proxy()
dashboard.reject(qp, reason="no thanks")
self.assertEqual([], called)
# Reject still writes a response + audit entry with empty diff.
resp = read_response(qp.queue_dir, qp.proposal.id)
self.assertEqual(STATUS_REJECTED, resp.status)
entries = read_audit_entries("cred-proxy", "dev")
entries = read_audit_entries("egress-proxy", "dev")
self.assertEqual(1, len(entries))
self.assertEqual("", entries[0].diff)
@@ -432,7 +432,7 @@ class TestCapabilityApplyWiring(_FakeHomeMixin, unittest.TestCase):
dashboard.approve(qp)
# capability-block has no audit log per PRD 0013 — its record
# lives in the per-bottle Dockerfile + transcript state.
self.assertEqual([], read_audit_entries("cred-proxy", "dev"))
self.assertEqual([], read_audit_entries("egress-proxy", "dev"))
self.assertEqual([], read_audit_entries("pipelock", "dev"))
def test_proposal_archived_after_apply(self):
@@ -464,7 +464,7 @@ class TestOperatorEditRoutes(_FakeHomeMixin, unittest.TestCase):
'{"routes": []}\n', content,
)
dashboard.operator_edit_routes("dev", '{"routes": [{"path": "/x/"}]}\n')
entries = read_audit_entries("cred-proxy", "dev")
entries = read_audit_entries("egress-proxy", "dev")
self.assertEqual(1, len(entries))
self.assertEqual(supervise.ACTION_OPERATOR_EDIT, entries[0].operator_action)
self.assertEqual("", entries[0].justification)
@@ -472,14 +472,14 @@ class TestOperatorEditRoutes(_FakeHomeMixin, unittest.TestCase):
def test_failure_does_not_write_audit(self):
dashboard.apply_routes_change = lambda slug, content: (_ for _ in ()).throw(
CredProxyApplyError("nope")
EgressProxyApplyError("nope")
)
with self.assertRaises(CredProxyApplyError):
with self.assertRaises(EgressProxyApplyError):
dashboard.operator_edit_routes("dev", '{"routes": []}\n')
self.assertEqual([], read_audit_entries("cred-proxy", "dev"))
self.assertEqual([], read_audit_entries("egress-proxy", "dev"))
class TestDiscoverCredProxySlugs(unittest.TestCase):
class TestDiscoverEgressProxySlugs(unittest.TestCase):
"""Slug-extraction parsing — exercises only the parsing path; the
docker ps invocation itself is environment-dependent (and tested
implicitly by the integration test)."""
@@ -491,7 +491,7 @@ class TestDiscoverCredProxySlugs(unittest.TestCase):
original = os.environ.get("PATH", "")
os.environ["PATH"] = "/nonexistent-no-docker-here"
try:
self.assertEqual([], dashboard.discover_cred_proxy_slugs())
self.assertEqual([], dashboard.discover_egress_proxy_slugs())
self.assertEqual([], dashboard.discover_pipelock_slugs())
finally:
os.environ["PATH"] = original
+3 -3
View File
@@ -12,7 +12,7 @@ from claude_bottle.cli import dashboard
from claude_bottle.supervise import (
Proposal,
TOOL_CAPABILITY_BLOCK,
TOOL_CRED_PROXY_BLOCK,
TOOL_EGRESS_PROXY_BLOCK,
TOOL_PIPELOCK_BLOCK,
sha256_hex,
)
@@ -46,9 +46,9 @@ class TestPipelockHostHighlight(unittest.TestCase):
green_lines = [text for text, attr in lines if attr == self.GREEN]
self.assertEqual(["api.github.com"], green_lines)
def test_no_green_lines_for_cred_proxy_block(self):
def test_no_green_lines_for_egress_proxy_block(self):
lines = dashboard._detail_lines(
_qp(TOOL_CRED_PROXY_BLOCK, '{"routes": []}'),
_qp(TOOL_EGRESS_PROXY_BLOCK, '{"routes": []}'),
green_attr=self.GREEN,
)
self.assertEqual([], [t for t, a in lines if a == self.GREEN])
+56
View File
@@ -0,0 +1,56 @@
"""Unit: validate_routes_content (PRD 0014 retargeted by PRD 0017
chunk 3). docker exec / cp / kill paths are covered by the
integration test."""
import unittest
from claude_bottle.backend.docker.egress_proxy_apply import (
EgressProxyApplyError,
validate_routes_content,
)
class TestValidateRoutesContent(unittest.TestCase):
def test_accepts_minimal_route_table(self):
validate_routes_content('{"routes": []}')
validate_routes_content(
'{"routes": [{"host": "api.github.com"}]}'
)
def test_accepts_full_route(self):
validate_routes_content(
'{"routes": [{"host": "api.github.com",'
' "path_allowlist": ["/repos/x/"],'
' "auth_scheme": "Bearer",'
' "token_env": "EGRESS_PROXY_TOKEN_0"}]}'
)
def test_rejects_bad_json(self):
with self.assertRaises(EgressProxyApplyError) as cm:
validate_routes_content("{not json")
self.assertIn("not valid", str(cm.exception))
def test_rejects_non_object_top_level(self):
with self.assertRaises(EgressProxyApplyError):
validate_routes_content("[]")
def test_rejects_missing_routes_key(self):
with self.assertRaises(EgressProxyApplyError):
validate_routes_content('{"other": []}')
def test_rejects_non_list_routes(self):
with self.assertRaises(EgressProxyApplyError):
validate_routes_content('{"routes": "not a list"}')
def test_rejects_partial_auth_pair(self):
# The addon-core parser enforces both-or-neither — the apply
# path picks this up before SIGHUP'ing the sidecar.
with self.assertRaises(EgressProxyApplyError):
validate_routes_content(
'{"routes": [{"host": "x.example",'
' "auth_scheme": "Bearer"}]}'
)
if __name__ == "__main__":
unittest.main()
+7 -7
View File
@@ -17,7 +17,7 @@ from claude_bottle.supervise import (
STATUS_MODIFIED,
STATUS_REJECTED,
TOOL_CAPABILITY_BLOCK,
TOOL_CRED_PROXY_BLOCK,
TOOL_EGRESS_PROXY_BLOCK,
TOOL_PIPELOCK_BLOCK,
archive_proposal,
audit_log_path,
@@ -37,7 +37,7 @@ from claude_bottle.supervise import (
FIXED_TS = datetime(2026, 5, 25, 12, 0, 0, tzinfo=timezone.utc)
def _proposal(tool: str = TOOL_CRED_PROXY_BLOCK, proposed: str = "{}", justification: str = "need a route") -> Proposal:
def _proposal(tool: str = TOOL_EGRESS_PROXY_BLOCK, proposed: str = "{}", justification: str = "need a route") -> Proposal:
return Proposal.new(
bottle_slug="dev",
tool=tool,
@@ -54,7 +54,7 @@ class TestProposalRoundtrip(unittest.TestCase):
self.assertTrue(p.id)
self.assertEqual("2026-05-25T12:00:00+00:00", p.arrival_timestamp)
self.assertEqual("dev", p.bottle_slug)
self.assertEqual(TOOL_CRED_PROXY_BLOCK, p.tool)
self.assertEqual(TOOL_EGRESS_PROXY_BLOCK, p.tool)
def test_to_from_dict_roundtrip(self):
p = _proposal()
@@ -139,13 +139,13 @@ class TestQueueIO(unittest.TestCase):
def test_list_pending_sorted_by_arrival(self):
# Fabricate two with explicit timestamps.
a = Proposal.new(
bottle_slug="dev", tool=TOOL_CRED_PROXY_BLOCK,
bottle_slug="dev", tool=TOOL_EGRESS_PROXY_BLOCK,
proposed_file="{}", justification="early",
current_file_hash="x",
now=datetime(2026, 5, 25, 10, 0, 0, tzinfo=timezone.utc),
)
b = Proposal.new(
bottle_slug="dev", tool=TOOL_CRED_PROXY_BLOCK,
bottle_slug="dev", tool=TOOL_EGRESS_PROXY_BLOCK,
proposed_file="{}", justification="late",
current_file_hash="x",
now=datetime(2026, 5, 25, 14, 0, 0, tzinfo=timezone.utc),
@@ -314,12 +314,12 @@ class TestDiffAndHash(unittest.TestCase):
class TestToolConstants(unittest.TestCase):
def test_tools_tuple_matches_individual_constants(self):
self.assertEqual(
(TOOL_CRED_PROXY_BLOCK, TOOL_PIPELOCK_BLOCK, TOOL_CAPABILITY_BLOCK),
(TOOL_EGRESS_PROXY_BLOCK, TOOL_PIPELOCK_BLOCK, TOOL_CAPABILITY_BLOCK),
supervise.TOOLS,
)
def test_component_map_covers_two_remediation_tools_only(self):
self.assertIn(TOOL_CRED_PROXY_BLOCK, supervise.COMPONENT_FOR_TOOL)
self.assertIn(TOOL_EGRESS_PROXY_BLOCK, supervise.COMPONENT_FOR_TOOL)
self.assertIn(TOOL_PIPELOCK_BLOCK, supervise.COMPONENT_FOR_TOOL)
self.assertNotIn(TOOL_CAPABILITY_BLOCK, supervise.COMPONENT_FOR_TOOL)
+11 -11
View File
@@ -45,19 +45,19 @@ from claude_bottle.supervise_server import (
class TestValidation(unittest.TestCase):
def test_cred_proxy_block_requires_valid_json(self):
def test_egress_proxy_block_requires_valid_json(self):
with self.assertRaises(_RpcError) as cm:
validate_proposed_file(_sv.TOOL_CRED_PROXY_BLOCK, "{not json")
validate_proposed_file(_sv.TOOL_EGRESS_PROXY_BLOCK, "{not json")
self.assertEqual(ERR_INVALID_PARAMS, cm.exception.code)
self.assertIn("not valid JSON", cm.exception.message)
def test_cred_proxy_block_requires_routes_array(self):
def test_egress_proxy_block_requires_routes_array(self):
with self.assertRaises(_RpcError):
validate_proposed_file(_sv.TOOL_CRED_PROXY_BLOCK, '{"other": []}')
validate_proposed_file(_sv.TOOL_EGRESS_PROXY_BLOCK, '{"other": []}')
def test_cred_proxy_block_accepts_valid_routes(self):
def test_egress_proxy_block_accepts_valid_routes(self):
validate_proposed_file(
_sv.TOOL_CRED_PROXY_BLOCK,
_sv.TOOL_EGRESS_PROXY_BLOCK,
'{"routes": [{"path": "/x/", "upstream": "https://example.com"}]}',
)
@@ -175,7 +175,7 @@ class TestHandleToolsList(unittest.TestCase):
names = [t["name"] for t in result["tools"]] # type: ignore[index]
self.assertEqual(
sorted([
_sv.TOOL_CRED_PROXY_BLOCK,
_sv.TOOL_EGRESS_PROXY_BLOCK,
_sv.TOOL_PIPELOCK_BLOCK,
_sv.TOOL_CAPABILITY_BLOCK,
]),
@@ -225,7 +225,7 @@ class TestHandleToolsCall(unittest.TestCase):
try:
result = handle_tools_call(
{
"name": _sv.TOOL_CRED_PROXY_BLOCK,
"name": _sv.TOOL_EGRESS_PROXY_BLOCK,
"arguments": {
"routes": '{"routes": []}',
"justification": "need a route",
@@ -269,7 +269,7 @@ class TestHandleToolsCall(unittest.TestCase):
with self.assertRaises(_RpcError):
handle_tools_call(
{
"name": _sv.TOOL_CRED_PROXY_BLOCK,
"name": _sv.TOOL_EGRESS_PROXY_BLOCK,
"arguments": {"routes": '{"routes": []}'},
},
self.config,
@@ -280,7 +280,7 @@ class TestHandleToolsCall(unittest.TestCase):
try:
handle_tools_call(
{
"name": _sv.TOOL_CRED_PROXY_BLOCK,
"name": _sv.TOOL_EGRESS_PROXY_BLOCK,
"arguments": {
"routes": '{"routes": []}',
"justification": "x",
@@ -367,7 +367,7 @@ class TestHttpEndToEnd(unittest.TestCase):
self.assertEqual("2.0", result["jsonrpc"])
self.assertEqual(1, result["id"])
names = [t["name"] for t in result["result"]["tools"]] # type: ignore[index]
self.assertIn(_sv.TOOL_CRED_PROXY_BLOCK, names)
self.assertIn(_sv.TOOL_EGRESS_PROXY_BLOCK, names)
def test_unknown_method_returns_jsonrpc_error(self):
result = self._post_jsonrpc(