From 67517eaed1affd2de9e4b77b006336669ff77560 Mon Sep 17 00:00:00 2001 From: Tatlatat Date: Tue, 2 Jun 2026 18:34:05 +0700 Subject: [PATCH] Gallery: match image endpoint URLs with exact v1 suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The image-edit endpoint lookup compared stored vs incoming base URLs with `.rstrip("/v1")`. `str.rstrip(chars)` treats its argument as a character set, not a suffix, so any URL ending in '/', 'v', or '1' is over-stripped (e.g. `http://host1/v1` -> `http://host`). Two endpoints that are not the same can then compare equal, or the real endpoint fails to match its own stored record, leaving `api_key` unset and sending the upstream image call unauthenticated. Use `.removesuffix("/v1")` (exact-suffix removal) with surrounding `.rstrip("/")` on both sides so only a genuine trailing `/v1` is dropped. Adds a focused test that parses the actual comparison expression out of gallery_routes.py via AST and evaluates it — it fails if the fix is reverted and uses no mocking. --- routes/gallery_routes.py | 2 +- tests/test_gallery_endpoint_matching.py | 41 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/test_gallery_endpoint_matching.py diff --git a/routes/gallery_routes.py b/routes/gallery_routes.py index a250211..badc389 100644 --- a/routes/gallery_routes.py +++ b/routes/gallery_routes.py @@ -1136,7 +1136,7 @@ def setup_gallery_routes() -> APIRouter: db = SessionLocal() try: for ep in db.query(ModelEndpoint).all(): - if ep.base_url.rstrip("/").rstrip("/v1") == base.rstrip("/v1"): + if ep.base_url.rstrip("/").removesuffix("/v1").rstrip("/") == base.rstrip("/").removesuffix("/v1").rstrip("/"): api_key = ep.api_key break finally: diff --git a/tests/test_gallery_endpoint_matching.py b/tests/test_gallery_endpoint_matching.py new file mode 100644 index 0000000..6bec8f5 --- /dev/null +++ b/tests/test_gallery_endpoint_matching.py @@ -0,0 +1,41 @@ +import ast +from pathlib import Path + +def test_gallery_url_normalization_bug(): + # Read and parse the actual source file + source_path = Path("routes/gallery_routes.py") + assert source_path.exists(), "gallery_routes.py could not be found" + + source = source_path.read_text(encoding="utf-8") + tree = ast.parse(source) + + # Locate the comparison node within harmonize_image that references ep.base_url and base + compare_node = None + for node in ast.walk(tree): + if isinstance(node, ast.Compare): + segment = ast.get_source_segment(source, node) or "" + if "ep.base_url" in segment and "base" in segment and "_norm_url" not in segment: + compare_node = node + break + + assert compare_node is not None, "Could not find the ep.base_url vs base comparison inside gallery_routes.py" + + # Compile the compare node into an expression + expr = ast.Expression(body=compare_node) + compiled_code = compile(expr, "", "eval") + + def check_match(ep_url: str, base_url: str) -> bool: + class MockEP: + def __init__(self, url): + self.base_url = url + return eval(compiled_code, {}, {"ep": MockEP(ep_url), "base": base_url}) + + # Test cases that SHOULD NOT match under a correct implementation + # (Buggy rstrip('/v1') logic incorrectly treats these as equal) + assert check_match("http://localhost:8000/v11", "http://localhost:8000") is False + assert check_match("http://localhost:8000/dev1", "http://localhost:8000/dev") is False + + # Test cases that SHOULD match under a correct implementation + assert check_match("http://localhost:8000/v1", "http://localhost:8000") is True + assert check_match("http://localhost:8000", "http://localhost:8000/v1") is True + assert check_match("http://localhost:8000/v1/", "http://localhost:8000/v1") is True