From be3c17218412f9893d01408178ad4ba92a1ba0fe Mon Sep 17 00:00:00 2001 From: mstarodu <65315675+mstarodu@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:30:43 +0200 Subject: [PATCH] Fix create table ignoring solution: send MSCRM.SolutionUniqueName header CreateEntities was called with SolutionUniqueName as a URL query parameter, which Dataverse silently ignores, so new tables always landed in the Default solution. Pass the documented MSCRM.SolutionUniqueName request header instead (matching the existing relationship-creation code) in the sync, async, and batch code paths. Co-Authored-By: Claude Fable 5 --- .../Dataverse/aio/data/_async_odata.py | 6 +++--- src/PowerPlatform/Dataverse/data/_odata.py | 6 +++--- src/PowerPlatform/Dataverse/data/_odata_base.py | 4 +++- tests/unit/aio/data/test_async_odata_internal.py | 4 ++-- tests/unit/data/test_odata_internal.py | 13 +++++++------ 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/PowerPlatform/Dataverse/aio/data/_async_odata.py b/src/PowerPlatform/Dataverse/aio/data/_async_odata.py index 33b29a48..a1eea138 100644 --- a/src/PowerPlatform/Dataverse/aio/data/_async_odata.py +++ b/src/PowerPlatform/Dataverse/aio/data/_async_odata.py @@ -862,10 +862,10 @@ async def _create_entity( } ] } - params = None + headers = None if solution_unique_name: - params = {"SolutionUniqueName": solution_unique_name} - await self._request("post", url, json=payload, params=params) + headers = {"MSCRM.SolutionUniqueName": solution_unique_name} + await self._request("post", url, json=payload, headers=headers) ent = await self._get_entity_by_table_schema_name( table_schema_name, headers={"Consistency": "Strong"}, diff --git a/src/PowerPlatform/Dataverse/data/_odata.py b/src/PowerPlatform/Dataverse/data/_odata.py index 03a9f367..7fd4b63c 100644 --- a/src/PowerPlatform/Dataverse/data/_odata.py +++ b/src/PowerPlatform/Dataverse/data/_odata.py @@ -841,10 +841,10 @@ def _create_entity( } ] } - params = None + headers = None if solution_unique_name: - params = {"SolutionUniqueName": solution_unique_name} - self._request("post", url, json=payload, params=params) + headers = {"MSCRM.SolutionUniqueName": solution_unique_name} + self._request("post", url, json=payload, headers=headers) ent = self._get_entity_by_table_schema_name( table_schema_name, headers={"Consistency": "Strong"}, diff --git a/src/PowerPlatform/Dataverse/data/_odata_base.py b/src/PowerPlatform/Dataverse/data/_odata_base.py index 182b307b..8fed2940 100644 --- a/src/PowerPlatform/Dataverse/data/_odata_base.py +++ b/src/PowerPlatform/Dataverse/data/_odata_base.py @@ -604,12 +604,14 @@ def _build_create_entity( ] } url = f"{self.api}/CreateEntities" + headers: Optional[Dict[str, str]] = None if solution: - url += f"?SolutionUniqueName={solution}" + headers = {"MSCRM.SolutionUniqueName": solution} return _RawRequest( method="POST", url=url, body=json.dumps(body, ensure_ascii=False), + headers=headers, ) def _build_delete_entity(self, metadata_id: str) -> _RawRequest: diff --git a/tests/unit/aio/data/test_async_odata_internal.py b/tests/unit/aio/data/test_async_odata_internal.py index d5c2fa55..2562d47d 100644 --- a/tests/unit/aio/data/test_async_odata_internal.py +++ b/tests/unit/aio/data/test_async_odata_internal.py @@ -1700,7 +1700,7 @@ class TestCreateEntityEdgeCases: """Coverage for _create_entity() solution_name, missing EntitySetName, missing MetadataId.""" async def test_create_entity_with_solution_unique_name(self): - """solution_unique_name is passed as a query parameter to the POST request.""" + """solution_unique_name is passed as the MSCRM.SolutionUniqueName header on the POST request.""" client = _make_client() client._request.return_value = _resp(status=204) entity_resp = { @@ -1718,7 +1718,7 @@ async def test_create_entity_with_solution_unique_name(self): solution_unique_name="MySolution", ) _, kwargs = client._request.call_args - assert kwargs.get("params", {}).get("SolutionUniqueName") == "MySolution" + assert kwargs.get("headers", {}).get("MSCRM.SolutionUniqueName") == "MySolution" assert result["EntitySetName"] == "new_tables" async def test_create_entity_missing_entity_set_name_raises(self): diff --git a/tests/unit/data/test_odata_internal.py b/tests/unit/data/test_odata_internal.py index 6f1cd106..3b509d6d 100644 --- a/tests/unit/data/test_odata_internal.py +++ b/tests/unit/data/test_odata_internal.py @@ -1338,12 +1338,12 @@ def side_effect(method, url, **kwargs): self.od._create_entity("new_TestTable", "Test Table", []) self.assertIn("MetadataId missing", str(ctx.exception)) - def test_solution_unique_name_passed_as_param(self): - """_create_entity passes SolutionUniqueName as query param when provided.""" + def test_solution_unique_name_passed_as_header(self): + """_create_entity passes MSCRM.SolutionUniqueName header when provided.""" self._setup_entity_creation() self.od._create_entity("new_TestTable", "Test Table", [], solution_unique_name="MySolution") post_call = next(c for c in self.od._request.call_args_list if c.args[0] == "post") - self.assertEqual(post_call.kwargs.get("params"), {"SolutionUniqueName": "MySolution"}) + self.assertEqual(post_call.kwargs.get("headers"), {"MSCRM.SolutionUniqueName": "MySolution"}) class TestGetAttributeMetadata(unittest.TestCase): @@ -2994,10 +2994,11 @@ def test_url_targets_create_entities(self): req = self.od._build_create_entity("new_TestTable", {}) self.assertTrue(req.url.endswith("/CreateEntities")) - def test_solution_appended_to_url(self): - """_build_create_entity appends SolutionUniqueName to URL when solution is given.""" + def test_solution_passed_as_header(self): + """_build_create_entity sets MSCRM.SolutionUniqueName header when solution is given.""" req = self.od._build_create_entity("new_TestTable", {}, solution="MySolution") - self.assertIn("SolutionUniqueName=MySolution", req.url) + self.assertEqual(req.headers, {"MSCRM.SolutionUniqueName": "MySolution"}) + self.assertNotIn("SolutionUniqueName", req.url) def test_no_solution_no_query_string(self): """_build_create_entity URL has no query string when solution is omitted."""