diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 091bd2284..be322ea1e 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.2.34" +version = "0.2.35" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/src/uipath/platform/connections/_connections_service.py b/packages/uipath-platform/src/uipath/platform/connections/_connections_service.py index b7c1e9444..3e6aee31c 100644 --- a/packages/uipath-platform/src/uipath/platform/connections/_connections_service.py +++ b/packages/uipath-platform/src/uipath/platform/connections/_connections_service.py @@ -32,6 +32,13 @@ _ORIGINATOR_VALUE = "uipath-python" +def _to_param_str(value: Any) -> str: + """Convert an activity parameter value to its wire string; booleans become lowercase.""" + if isinstance(value, bool): + return "true" if value else "false" + return value if isinstance(value, str) else str(value) + + class ConnectionsService(BaseService): """Service for managing UiPath external service connections. @@ -754,7 +761,7 @@ def _build_activity_request_spec( if value is None: continue - value_str = str(value) if not isinstance(value, str) else value + value_str = _to_param_str(value) if param_name in activity_metadata.parameter_location_info.query_params: query_params[param_name] = value_str @@ -812,7 +819,7 @@ def _build_activity_request_spec( # Scalar (string/number/etc.) — send as a plain multipart # form field, not a file part. The (None, value) shape tells # httpx to omit `filename=...` from the Content-Disposition. - files[key] = (None, str(val)) + files[key] = (None, _to_param_str(val)) files[json_section] = ( "", diff --git a/packages/uipath-platform/tests/services/test_connections_service.py b/packages/uipath-platform/tests/services/test_connections_service.py index af74984b8..c9fa45441 100644 --- a/packages/uipath-platform/tests/services/test_connections_service.py +++ b/packages/uipath-platform/tests/services/test_connections_service.py @@ -1560,6 +1560,65 @@ def test_invoke_activity_with_path_parameters( "/elements_/v3/element/instances/test-connection-123/elements/test-connector/users/user456/posts/post789" ) + def test_invoke_activity_bool_query_and_header_params_are_lowercase( + self, + httpx_mock: HTTPXMock, + service: ConnectionsService, + simple_activity_metadata: ActivityMetadata, + ) -> None: + """Test that booleans are sent as true/false, not Python's True/False.""" + connection_id = "test-connection-123" + activity_input = { + "query_param": True, + "query_param2": False, + "custom_header": True, + "custom_header2": 3, + } + + httpx_mock.add_response( + method="GET", + status_code=200, + json={"id": connection_id, "name": "Test", "elementInstanceId": 1}, + ) + httpx_mock.add_response(method="POST", status_code=200, json={}) + + service.invoke_activity( + activity_metadata=simple_activity_metadata, + connection_id=connection_id, + activity_input=activity_input, + ) + + sent_request = httpx_mock.get_requests()[1] + assert sent_request.url.params["query_param"] == "true" + assert sent_request.url.params["query_param2"] == "false" + assert sent_request.headers["custom_header"] == "true" + assert sent_request.headers["custom_header2"] == "3" + + def test_invoke_activity_bool_path_param_is_lowercase( + self, + httpx_mock: HTTPXMock, + service: ConnectionsService, + path_activity_metadata: ActivityMetadata, + ) -> None: + """Test that a boolean path parameter is substituted as false, not False.""" + connection_id = "test-connection-123" + + httpx_mock.add_response( + method="GET", + status_code=200, + json={"id": connection_id, "name": "Test", "elementInstanceId": 1}, + ) + httpx_mock.add_response(method="POST", status_code=200, json={}) + + service.invoke_activity( + activity_metadata=path_activity_metadata, + connection_id=connection_id, + activity_input={"userId": False, "postId": 7}, + ) + + sent_request = httpx_mock.get_requests()[1] + assert sent_request.url.path.endswith("/users/false/posts/7") + def test_invoke_activity_multipart_request( self, httpx_mock: HTTPXMock, @@ -2343,3 +2402,39 @@ def test_invoke_activity_multipart_scalar_is_plain_form_field( # Scalar payload must NOT carry a filename in Content-Disposition. assert "filename=" not in payload_part assert "{}" in payload_part + + def test_invoke_activity_multipart_bool_scalar_is_lowercase( + self, + httpx_mock: HTTPXMock, + service: ConnectionsService, + ) -> None: + """Boolean multipart form fields are sent as true/false, not True/False.""" + metadata = ActivityMetadata( + object_path="/elements/test-connector/upload", + method_name="POST", + content_type="multipart/form-data", + parameter_location_info=ActivityParameterLocationInfo( + multipart_params=["flag"], + body_fields=[], + ), + ) + connection_id = "test-connection-123" + + httpx_mock.add_response( + method="GET", + status_code=200, + json={"id": connection_id, "name": "Test", "elementInstanceId": 1}, + ) + httpx_mock.add_response(method="POST", status_code=200, json={"ok": True}) + + service.invoke_activity( + activity_metadata=metadata, + connection_id=connection_id, + activity_input={"flag": True}, + ) + + sent_request = httpx_mock.get_requests()[1] + boundary = sent_request.headers["content-type"].split("boundary=")[1] + flag_part = _multipart_part(sent_request.content, boundary, "flag") + + assert flag_part.rstrip().endswith("true") diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index 8d7cccdcc..b02fd2c40 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.2.34" +version = "0.2.35" source = { editable = "." } dependencies = [ { name = "anyio" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index eb6d18ede..9887fc03a 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2762,7 +2762,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.2.34" +version = "0.2.35" source = { editable = "../uipath-platform" } dependencies = [ { name = "anyio" },