Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
from unittest.mock import MagicMock

import httpx
import pytest

from end_to_end_tests.functional_tests.helpers import (
with_generated_client_fixture,
with_generated_code_import,
)


@with_generated_client_fixture(
"""
paths:
"/items/{item_id}/details/{detail_id}":
get:
operationId: getItemDetail
parameters:
- name: item_id
in: path
required: true
schema:
type: string
- name: detail_id
in: path
required: true
schema:
type: string
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
id:
type: string
""")
@with_generated_code_import(".api.default.get_item_detail.sync_detailed")
@with_generated_code_import(".client.Client")
class TestPathParameterEncoding:
"""Test that path parameters are properly URL-encoded"""

def test_path_params_with_normal_chars_work(self, sync_detailed, Client):
"""Test that normal alphanumeric path parameters still work correctly"""
mock_httpx_client = MagicMock(spec=httpx.Client)
mock_response = MagicMock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json.return_value = {"id": "test"}
mock_response.content = b'{"id": "test"}'
mock_response.headers = {}
mock_httpx_client.request.return_value = mock_response

client = Client(base_url="https://api.example.com")
client.set_httpx_client(mock_httpx_client)

sync_detailed(
item_id="item123",
detail_id="detail456",
client=client,
)

mock_httpx_client.request.assert_called_once()
call_kwargs = mock_httpx_client.request.call_args[1]

# Normal characters should remain unchanged
expected_url = "/items/item123/details/detail456"
assert call_kwargs["url"] == expected_url

def test_path_params_with_reserved_chars_are_encoded(self, sync_detailed, Client):
"""Test that path parameters with reserved characters are properly URL-encoded"""
# Create a mock httpx client
mock_httpx_client = MagicMock(spec=httpx.Client)
mock_response = MagicMock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json.return_value = {"id": "test"}
mock_response.content = b'{"id": "test"}'
mock_response.headers = {}
mock_httpx_client.request.return_value = mock_response

# Create a client with the mock httpx client
client = Client(base_url="https://api.example.com")
client.set_httpx_client(mock_httpx_client)

# Call the endpoint with path parameters containing reserved characters
sync_detailed(
item_id="item/with/slashes",
detail_id="detail?with=query&chars",
client=client,
)

# Verify the request was made with properly encoded URL
mock_httpx_client.request.assert_called_once()
call_kwargs = mock_httpx_client.request.call_args[1]

# The URL should have encoded slashes and query characters
expected_url = "/items/item%2Fwith%2Fslashes/details/detail%3Fwith%3Dquery%26chars"
assert call_kwargs["url"] == expected_url

def test_path_params_with_spaces_are_encoded(self, sync_detailed, Client):
"""Test that path parameters with spaces are properly URL-encoded"""
mock_httpx_client = MagicMock(spec=httpx.Client)
mock_response = MagicMock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json.return_value = {"id": "test"}
mock_response.content = b'{"id": "test"}'
mock_response.headers = {}
mock_httpx_client.request.return_value = mock_response

client = Client(base_url="https://api.example.com")
client.set_httpx_client(mock_httpx_client)

sync_detailed(
item_id="item with spaces",
detail_id="detail with spaces",
client=client,
)

mock_httpx_client.request.assert_called_once()
call_kwargs = mock_httpx_client.request.call_args[1]

# Spaces should be encoded as %20
expected_url = "/items/item%20with%20spaces/details/detail%20with%20spaces"
assert call_kwargs["url"] == expected_url

def test_path_params_with_hash_are_encoded(self, sync_detailed, Client):
"""Test that path parameters with hash/fragment characters are properly URL-encoded"""
mock_httpx_client = MagicMock(spec=httpx.Client)
mock_response = MagicMock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json.return_value = {"id": "test"}
mock_response.content = b'{"id": "test"}'
mock_response.headers = {}
mock_httpx_client.request.return_value = mock_response

client = Client(base_url="https://api.example.com")
client.set_httpx_client(mock_httpx_client)

sync_detailed(
item_id="item#1",
detail_id="detail#id",
client=client,
)

mock_httpx_client.request.assert_called_once()
call_kwargs = mock_httpx_client.request.call_args[1]

# Hash should be encoded as %23
expected_url = "/items/item%231/details/detail%23id"
assert call_kwargs["url"] == expected_url
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPStatus
from typing import Any
from urllib.parse import quote

import httpx

Expand All @@ -13,7 +14,9 @@ def _get_kwargs(
) -> dict[str, Any]:
_kwargs: dict[str, Any] = {
"method": "get",
"url": f"/naming/{hyphen_in_path}",
"url": "/naming/{hyphen_in_path}".format(
hyphen_in_path=quote(str(hyphen_in_path), safe=""),
),
}

return _kwargs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPStatus
from typing import Any
from urllib.parse import quote

import httpx

Expand Down Expand Up @@ -34,7 +35,9 @@ def _get_kwargs(

_kwargs: dict[str, Any] = {
"method": "get",
"url": f"/parameter-references/{path_param}",
"url": "/parameter-references/{path_param}".format(
path_param=quote(str(path_param), safe=""),
),
"params": params,
"cookies": cookies,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPStatus
from typing import Any
from urllib.parse import quote

import httpx

Expand All @@ -21,7 +22,9 @@ def _get_kwargs(

_kwargs: dict[str, Any] = {
"method": "delete",
"url": f"/common_parameters_overriding/{param_path}",
"url": "/common_parameters_overriding/{param_path}".format(
param_path=quote(str(param_path), safe=""),
),
"params": params,
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPStatus
from typing import Any
from urllib.parse import quote

import httpx

Expand All @@ -21,7 +22,9 @@ def _get_kwargs(

_kwargs: dict[str, Any] = {
"method": "get",
"url": f"/common_parameters_overriding/{param_path}",
"url": "/common_parameters_overriding/{param_path}".format(
param_path=quote(str(param_path), safe=""),
),
"params": params,
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPStatus
from typing import Any
from urllib.parse import quote

import httpx

Expand Down Expand Up @@ -31,7 +32,9 @@ def _get_kwargs(

_kwargs: dict[str, Any] = {
"method": "get",
"url": f"/same-name-multiple-locations/{param_path}",
"url": "/same-name-multiple-locations/{param_path}".format(
param_path=quote(str(param_path), safe=""),
),
"params": params,
"cookies": cookies,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPStatus
from typing import Any
from urllib.parse import quote

import httpx

Expand All @@ -16,7 +17,12 @@ def _get_kwargs(
) -> dict[str, Any]:
_kwargs: dict[str, Any] = {
"method": "get",
"url": f"/multiple-path-parameters/{param4}/something/{param2}/{param1}/{param3}",
"url": "/multiple-path-parameters/{param4}/something/{param2}/{param1}/{param3}".format(
param4=quote(str(param4), safe=""),
param2=quote(str(param2), safe=""),
param1=quote(str(param1), safe=""),
param3=quote(str(param3), safe=""),
),
}

return _kwargs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPStatus
from typing import Any, Literal, cast
from urllib.parse import quote

import httpx

Expand Down Expand Up @@ -28,7 +29,9 @@ def _get_kwargs(

_kwargs: dict[str, Any] = {
"method": "post",
"url": f"/const/{path}",
"url": "/const/{path}".format(
path=quote(str(path), safe=""),
),
"params": params,
}

Expand Down
3 changes: 2 additions & 1 deletion openapi_python_client/templates/endpoint_module.py.jinja
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPStatus
from typing import Any, cast
from urllib.parse import quote

import httpx

Expand Down Expand Up @@ -31,7 +32,7 @@ def _get_kwargs(
{% if endpoint.path_parameters %}
"url": "{{ endpoint.path }}".format(
{%- for parameter in endpoint.path_parameters -%}
{{parameter.python_name}}={{parameter.python_name}},
{{parameter.python_name}}=quote(str({{parameter.python_name}}), safe=""),
{%- endfor -%}
),
{% else %}
Expand Down