From 1ccb7eaf85b4982f985e6f648ca93499844badc2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Jul 2026 20:15:14 +0000 Subject: [PATCH 1/2] Make browser and PostgREST tests run instead of skipping - test_html_ui: fall back to any Chromium found under PLAYWRIGHT_BROWSERS_PATH when the pinned Playwright build number is not present, so the XSS-rendering test actually executes; still skips only if no browser exists at all. - test_postgrest_integration: when TEST_POSTGREST_URL is unset, stand up an in-process PostgREST-compatible shim that resolves GET /rpc/latest_output against the real latest_output() function, exercising the CLI's request construction and the SQL contract without an external gateway. A live PostgREST instance is still used when configured. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01V6C2eZvbEWJxAWqMxFPLHi --- tests/test_html_ui.py | 30 +++++++- tests/test_postgrest_integration.py | 103 +++++++++++++++++++++++++--- 2 files changed, 123 insertions(+), 10 deletions(-) diff --git a/tests/test_html_ui.py b/tests/test_html_ui.py index 958f814..55251ac 100644 --- a/tests/test_html_ui.py +++ b/tests/test_html_ui.py @@ -2,7 +2,9 @@ import contextlib import functools +import glob import http.server +import os import threading from pathlib import Path @@ -19,6 +21,32 @@ HTML_DIR = Path(__file__).parents[1] / "html" +def _launch_chromium(playwright): + """Launch Chromium, tolerating a browser build that predates this + Playwright release's pinned revision. + + ``playwright.chromium.launch()`` resolves an exact browser build number and + fails when only a differently numbered Chromium is present in the image (a + common situation when the browser is provisioned separately from the pinned + Python package). Fall back to any executable Chromium found under + ``PLAYWRIGHT_BROWSERS_PATH`` before giving up. + """ + try: + return playwright.chromium.launch() + except Exception: + root = os.environ.get("PLAYWRIGHT_BROWSERS_PATH", "/opt/pw-browsers") + patterns = ( + "chromium-*/chrome-linux/chrome", + "chromium_headless_shell-*/chrome-linux/headless_shell", + "chromium", + ) + for pattern in patterns: + for path in sorted(glob.glob(os.path.join(root, pattern))): + if os.access(path, os.X_OK): + return playwright.chromium.launch(executable_path=path) + raise + + @pytest.fixture(scope="module") def ui_base_url(): handler = functools.partial( @@ -43,7 +71,7 @@ def test_command_and_response_markup_is_rendered_only_as_text(ui_base_url): with sync_playwright() as playwright: try: - browser = playwright.chromium.launch() + browser = _launch_chromium(playwright) except Exception as error: pytest.skip(f"Playwright Chromium is unavailable: {error}") diff --git a/tests/test_postgrest_integration.py b/tests/test_postgrest_integration.py index f44c479..eb04e47 100644 --- a/tests/test_postgrest_integration.py +++ b/tests/test_postgrest_integration.py @@ -1,28 +1,113 @@ -"""Integration coverage for requests sent to a live PostgREST instance.""" +"""Integration coverage for requests sent to a PostgREST-compatible endpoint. +When ``TEST_POSTGREST_URL`` points at a live PostgREST instance the test uses +it directly. Otherwise an in-process, PostgREST-compatible shim resolves +``GET /rpc/latest_output`` by invoking the real ``latest_output`` SQL function, +so the CLI's request construction and the function's contract are exercised +without provisioning an external gateway. +""" + +import json import os +import threading import uuid +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from urllib.parse import parse_qs, urlparse +import psycopg2 +from psycopg2.extras import RealDictCursor import pytest from cli.shell_cli import tail_output -def test_tail_output_invokes_latest_output_through_postgrest(db_conn, capsys): - """A CLI-generated query resolves latest_output(UUID, INTEGER).""" - base_url = os.environ.get("TEST_POSTGREST_URL") - if not base_url: - pytest.skip("TEST_POSTGREST_URL not set") +def _make_handler(dsn): + class Handler(BaseHTTPRequestHandler): + def do_GET(self): + parsed = urlparse(self.path) + if parsed.path != "/rpc/latest_output": + self.send_error(404) + return + params = parse_qs(parsed.query) + try: + user_id = params["p_user_id"][0] + session_id = params["p_session_id"][0] + except (KeyError, IndexError): + self.send_error(400) + return + since_id = int(params.get("p_since_id", ["0"])[0]) + + conn = psycopg2.connect(dsn) + try: + conn.autocommit = True + with conn.cursor(cursor_factory=RealDictCursor) as cur: + cur.execute( + "SELECT * FROM latest_output(%s, %s, %s)", + (user_id, session_id, since_id), + ) + rows = cur.fetchall() + finally: + conn.close() + + body = json.dumps(rows, default=str).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, *args): # pragma: no cover - silence test server + pass + + return Handler + + +@pytest.fixture +def postgrest_base_url(): + """Yield a base URL exposing PostgREST's ``/rpc`` interface. + + Prefers a live PostgREST instance via ``TEST_POSTGREST_URL``; falls back to + an in-process shim backed by the test database. + """ + configured = os.environ.get("TEST_POSTGREST_URL") + if configured: + yield configured.rstrip("/") + return + dsn = os.environ.get("TEST_DATABASE_URL") + if not dsn: + pytest.skip("TEST_DATABASE_URL not set") + + server = ThreadingHTTPServer(("127.0.0.1", 0), _make_handler(dsn)) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + yield f"http://127.0.0.1:{server.server_port}" + finally: + server.shutdown() + thread.join() + server.server_close() + + +def test_tail_output_invokes_latest_output_through_postgrest( + db_conn, postgrest_base_url, capsys +): + """A CLI-generated query resolves latest_output(UUID, UUID, INTEGER).""" user_id = str(uuid.uuid4()) with db_conn.cursor() as cur: cur.execute( "INSERT INTO users(id, username) VALUES (%s, %s)", (user_id, f"postgrest-{user_id}"), ) - cur.execute("INSERT INTO environments(user_id) VALUES (%s) RETURNING session_id", (user_id,)) + cur.execute( + "INSERT INTO environments(user_id) VALUES (%s) RETURNING session_id", + (user_id,), + ) session_id = str(cur.fetchone()[0]) - cur.execute("SELECT submit_command(%s, %s, %s)", (user_id, session_id, "echo integration")) + cur.execute( + "SELECT submit_command(%s, %s, %s)", + (user_id, session_id, "echo integration"), + ) command_id = cur.fetchone()[0] cur.execute( """ @@ -35,7 +120,7 @@ def test_tail_output_invokes_latest_output_through_postgrest(db_conn, capsys): ) assert tail_output( - base_url.rstrip("/"), user_id, session_id, since=0, interval=0, max_polls=1 + postgrest_base_url, user_id, session_id, since=0, interval=0, max_polls=1 ) == 0 assert capsys.readouterr().out.splitlines() == [ "$ echo integration", From 0d64be07490a82df18457d351c5f4db5febf0259 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Jul 2026 20:28:41 +0000 Subject: [PATCH 2/2] Reconnect executor workers with the full DSN, not conn.dsn The two multi-worker integration tests opened extra connections via psycopg2.connect(db_conn.dsn). psycopg2 masks the password in connection.dsn (password=xxx), so reconnecting failed with 'password authentication failed' under scram-sha-256 auth (as used by the CI Postgres service). Local trust auth hid this. Reconnect using TEST_DATABASE_URL instead so the real credentials are used. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01V6C2eZvbEWJxAWqMxFPLHi --- tests/test_executor_integration.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_executor_integration.py b/tests/test_executor_integration.py index 74a0fb0..deb51b0 100644 --- a/tests/test_executor_integration.py +++ b/tests/test_executor_integration.py @@ -134,8 +134,8 @@ def test_queued_commands_use_sequential_per_user_environment( cur.execute("SELECT submit_command(%s, %s, %s)", (user_id, session_id, "pwd")) pwd_id = cur.fetchone()[0] - worker_one = psycopg2.connect(db_conn.dsn) - worker_two = psycopg2.connect(db_conn.dsn) + worker_one = psycopg2.connect(os.environ["TEST_DATABASE_URL"]) + worker_two = psycopg2.connect(os.environ["TEST_DATABASE_URL"]) try: first = fetch_pending(worker_one) assert first["id"] == cd_id @@ -180,7 +180,7 @@ def test_expired_command_is_recovered_and_no_longer_blocks_session(db_conn): (abandoned_id,), ) - worker = psycopg2.connect(db_conn.dsn) + worker = psycopg2.connect(os.environ["TEST_DATABASE_URL"]) try: recovered = fetch_pending(worker, "fresh") assert recovered["id"] == abandoned_id