From 8a6cbcf717e2267fb1fea8951e7f80c1e6e6b856 Mon Sep 17 00:00:00 2001 From: Henry Park Date: Tue, 28 Apr 2026 20:45:04 -0700 Subject: [PATCH] test: update approval e2e expectations (#3054) --- tests/e2e/scenarios/test_tool_approval.py | 29 ++++++++++++++++++++++- tests/e2e_builtin_tool_coverage.rs | 2 +- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/e2e/scenarios/test_tool_approval.py b/tests/e2e/scenarios/test_tool_approval.py index 7842e8ee57..fd70cd6d75 100644 --- a/tests/e2e/scenarios/test_tool_approval.py +++ b/tests/e2e/scenarios/test_tool_approval.py @@ -2,6 +2,9 @@ import asyncio import json +from urllib.parse import urlsplit + +import httpx from helpers import ( AUTH_TOKEN, @@ -37,6 +40,22 @@ async def _send_chat_message(base_url: str, thread_id: str, content: str) -> Non assert response.status_code == 202, response.text +def _page_base_url(page) -> str: + parsed = urlsplit(page.url) + return f"{parsed.scheme}://{parsed.netloc}" + + +async def _require_http_approval(base_url: str) -> None: + async with httpx.AsyncClient() as client: + response = await client.put( + f"{base_url}/api/settings/tools/http", + json={"state": "ask_each_time"}, + headers={"Authorization": f"Bearer {AUTH_TOKEN}"}, + timeout=15, + ) + assert response.status_code == 200, response.text + + async def _wait_for_history( base_url: str, thread_id: str, @@ -202,10 +221,11 @@ async def test_waiting_for_approval_message_no_error_prefix(page): approval is pending. The backend should reject the second input with a non-error status that includes the pending tool context. """ + await _require_http_approval(_page_base_url(page)) assistant_messages = page.locator(SEL["message_assistant"]) chat_input = await ensure_writable_chat_input(page) - # Trigger a real HTTP tool call that pauses for approval in the default E2E harness. + # Trigger a real HTTP tool call that pauses for approval in this test harness. await chat_input.fill("make approval post approval-required") await chat_input.press("Enter") @@ -252,6 +272,7 @@ async def test_waiting_for_approval_message_no_error_prefix(page): async def test_chat_reply_approve_resumes_pending_tool(ironclaw_server): """A plain chat reply of 'approve' should resume the pending tool call.""" + await _require_http_approval(ironclaw_server) thread_id = await _create_thread(ironclaw_server) await _send_chat_message(ironclaw_server, thread_id, "make approval post approval-chat") @@ -272,6 +293,7 @@ async def test_chat_reply_approve_resumes_pending_tool(ironclaw_server): async def test_chat_reply_deny_rejects_pending_tool(ironclaw_server): """A plain chat reply of 'deny' should reject the pending tool call.""" + await _require_http_approval(ironclaw_server) thread_id = await _create_thread(ironclaw_server) await _send_chat_message(ironclaw_server, thread_id, "make approval post approval-denied") @@ -293,6 +315,7 @@ async def test_chat_reply_deny_rejects_pending_tool(ironclaw_server): async def test_text_approval_resolves_real_tool_call(page): """Typing 'yes' should resolve a real approval gate triggered by a tool call.""" + await _require_http_approval(_page_base_url(page)) chat_input = page.locator(SEL["chat_input"]) await chat_input.wait_for(state="visible", timeout=5000) @@ -322,6 +345,7 @@ async def test_text_approval_resolves_real_tool_call(page): async def test_slash_approve_is_thread_scoped_api(ironclaw_server): """Sending '/approve' in thread A must not resolve a pending gate in thread B.""" + await _require_http_approval(ironclaw_server) thread_a = await _create_thread(ironclaw_server) thread_b = await _create_thread(ironclaw_server) @@ -357,6 +381,7 @@ async def test_slash_approve_is_thread_scoped_api(ironclaw_server): # All tests that need the http approval gate to fire MUST run before this one. async def test_chat_reply_always_auto_approves_next_same_tool(ironclaw_server): """A plain chat reply of 'always' should auto-approve the same tool next time.""" + await _require_http_approval(ironclaw_server) thread_id = await _create_thread(ironclaw_server) await _send_chat_message(ironclaw_server, thread_id, "make approval post approval-always-a") @@ -626,6 +651,7 @@ async def test_normal_text_not_intercepted_with_approval_card(page): async def test_text_approval_resolves_real_tool_call(browser, managed_gateway_server): """Typing 'yes' should resolve a real approval gate triggered by a tool call.""" + await _require_http_approval(managed_gateway_server.base_url) context = await browser.new_context(viewport={"width": 1280, "height": 720}) page = await context.new_page() try: @@ -838,6 +864,7 @@ async def test_slash_approve_does_not_intercept_other_thread_card(page): async def test_slash_approve_is_thread_scoped_api(managed_gateway_server): """Sending '/approve' in thread A must not resolve a pending gate in thread B.""" base_url = managed_gateway_server.base_url + await _require_http_approval(base_url) thread_a = await _create_thread(base_url) thread_b = await _create_thread(base_url) diff --git a/tests/e2e_builtin_tool_coverage.rs b/tests/e2e_builtin_tool_coverage.rs index d0e338b88f..7263b5e307 100644 --- a/tests/e2e_builtin_tool_coverage.rs +++ b/tests/e2e_builtin_tool_coverage.rs @@ -1222,7 +1222,7 @@ mod tests { .expect("tool_search description should be a string"); assert!( tool_search_description.contains("`tool_install`") - && tool_search_description.contains("`tool_activate`"), + && tool_search_description.contains("`tool_activate(name=\"...\")`"), "tool_search description should describe setup/activation via tool_install and \ tool_activate: {tool_search_description}" );