From 4063ec073a561a2f2ac8fade5d1aa254df0780a7 Mon Sep 17 00:00:00 2001 From: Andrii Novikov Date: Wed, 13 May 2026 14:29:45 +0300 Subject: [PATCH 1/3] fix: preserve trailing slash on relative URLs #80 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BaseHTTPClient._prepare_url was stripping the trailing `/` from the joined path, breaking endpoints where the slash is semantically meaningful (e.g. folder listings under `/v1/metadata/files/...`). The rstrip was redundant — `lstrip` on the relative side already handles the join boundary against the always-trailing-slash base_url. --- aidial_client/_http_client/_base.py | 2 +- tests/test_prepare_url.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/test_prepare_url.py diff --git a/aidial_client/_http_client/_base.py b/aidial_client/_http_client/_base.py index d2f834e..19adf22 100644 --- a/aidial_client/_http_client/_base.py +++ b/aidial_client/_http_client/_base.py @@ -51,7 +51,7 @@ def _prepare_url(self, url: str) -> httpx.URL: merge_raw_path = ( self.base_url.raw_path + parsed_url.raw_path.lstrip(b"/") ) - return self.base_url.copy_with(raw_path=merge_raw_path.rstrip(b"/")) + return self.base_url.copy_with(raw_path=merge_raw_path) return parsed_url def _build_request( diff --git a/tests/test_prepare_url.py b/tests/test_prepare_url.py new file mode 100644 index 0000000..ba7a468 --- /dev/null +++ b/tests/test_prepare_url.py @@ -0,0 +1,22 @@ +import pytest + +from aidial_client import AsyncDial, Dial + + +@pytest.fixture(params=[Dial, AsyncDial]) +def client(request): + return request.param(base_url="http://localhost:8090", api_key="dummy") + + +@pytest.mark.parametrize( + "relative,expected_path", + [ + ("v1/metadata/files/bucket/appdata/app/", "/v1/metadata/files/bucket/appdata/app/"), + ("v1/metadata/files/bucket/appdata/app", "/v1/metadata/files/bucket/appdata/app"), + ("/v1/foo/", "/v1/foo/"), + ("v1/x/?foo=1", "/v1/x/"), + ], +) +def test_prepare_url_preserves_trailing_slash(client, relative, expected_path): + prepared = client._http_client._prepare_url(relative) + assert prepared.raw_path.decode().split("?")[0] == expected_path From a90776cd06451c8274033ba3724af4a061beb771 Mon Sep 17 00:00:00 2001 From: Andrii Novikov Date: Wed, 13 May 2026 14:39:08 +0300 Subject: [PATCH 2/3] test: assert full prepared URL instead of just the path --- tests/test_prepare_url.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/test_prepare_url.py b/tests/test_prepare_url.py index ba7a468..8e7d813 100644 --- a/tests/test_prepare_url.py +++ b/tests/test_prepare_url.py @@ -2,21 +2,31 @@ from aidial_client import AsyncDial, Dial +BASE_URL = "http://localhost:8090" + @pytest.fixture(params=[Dial, AsyncDial]) def client(request): - return request.param(base_url="http://localhost:8090", api_key="dummy") + return request.param(base_url=BASE_URL, api_key="dummy") @pytest.mark.parametrize( - "relative,expected_path", + "relative,expected_url", [ - ("v1/metadata/files/bucket/appdata/app/", "/v1/metadata/files/bucket/appdata/app/"), - ("v1/metadata/files/bucket/appdata/app", "/v1/metadata/files/bucket/appdata/app"), - ("/v1/foo/", "/v1/foo/"), - ("v1/x/?foo=1", "/v1/x/"), + ( + "v1/metadata/files/bucket/appdata/app/", + f"{BASE_URL}/v1/metadata/files/bucket/appdata/app/", + ), + ( + "v1/metadata/files/bucket/appdata/app", + f"{BASE_URL}/v1/metadata/files/bucket/appdata/app", + ), + ("/v1/foo/", f"{BASE_URL}/v1/foo/"), + ("v1/x/?foo=1", f"{BASE_URL}/v1/x/?foo=1"), + ('/', f"{BASE_URL}/"), + ('', f"{BASE_URL}/"), ], ) -def test_prepare_url_preserves_trailing_slash(client, relative, expected_path): +def test_prepare_url_preserves_trailing_slash(client, relative, expected_url): prepared = client._http_client._prepare_url(relative) - assert prepared.raw_path.decode().split("?")[0] == expected_path + assert str(prepared) == expected_url From 38e3d4d53cf917fc20539056a15f364125b2ea76 Mon Sep 17 00:00:00 2001 From: Andrii Novikov Date: Wed, 13 May 2026 15:43:10 +0300 Subject: [PATCH 3/3] chore: fix linter --- tests/test_prepare_url.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_prepare_url.py b/tests/test_prepare_url.py index 8e7d813..4c47e67 100644 --- a/tests/test_prepare_url.py +++ b/tests/test_prepare_url.py @@ -23,8 +23,8 @@ def client(request): ), ("/v1/foo/", f"{BASE_URL}/v1/foo/"), ("v1/x/?foo=1", f"{BASE_URL}/v1/x/?foo=1"), - ('/', f"{BASE_URL}/"), - ('', f"{BASE_URL}/"), + ("/", f"{BASE_URL}/"), + ("", f"{BASE_URL}/"), ], ) def test_prepare_url_preserves_trailing_slash(client, relative, expected_url):