Skip to content

Commit fff4185

Browse files
author
Samson Gebre
committed
Merge remote-tracking branch 'origin/main' into users/sagebree/batch
2 parents fa16258 + a1bf92f commit fff4185

2 files changed

Lines changed: 40 additions & 5 deletions

File tree

src/PowerPlatform/Dataverse/data/_odata.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -453,12 +453,11 @@ def _upsert_multiple(
453453
}
454454
if conflicting:
455455
raise ValueError(f"record payload conflicts with alternate_key on fields: {sorted(conflicting)!r}")
456-
combined: Dict[str, Any] = {**alt_key_lower, **record_processed}
457-
if "@odata.type" not in combined:
458-
combined["@odata.type"] = f"Microsoft.Dynamics.CRM.{logical_name}"
456+
if "@odata.type" not in record_processed:
457+
record_processed["@odata.type"] = f"Microsoft.Dynamics.CRM.{logical_name}"
459458
key_str = self._build_alternate_key_str(alt_key)
460-
combined["@odata.id"] = f"{entity_set}({key_str})"
461-
targets.append(combined)
459+
record_processed["@odata.id"] = f"{entity_set}({key_str})"
460+
targets.append(record_processed)
462461
payload = {"Targets": targets}
463462
url = f"{self.api}/{entity_set}/Microsoft.Dynamics.CRM.UpsertMultiple"
464463
self._request("post", url, json=payload, expected=(200, 201, 204))

tests/unit/data/test_odata_internal.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,42 @@ def test_equal_lengths_does_not_raise(self):
5757
self.assertEqual(len(post_calls), 1)
5858
self.assertIn("UpsertMultiple", post_calls[0].args[1])
5959

60+
def test_payload_excludes_alternate_key_fields(self):
61+
"""Alternate key fields must NOT appear in the request body (only in @odata.id)."""
62+
self.od._upsert_multiple(
63+
"accounts",
64+
"account",
65+
[{"accountnumber": "ACC-001"}],
66+
[{"name": "Contoso", "telephone1": "555-0100"}],
67+
)
68+
post_calls = [c for c in self.od._request.call_args_list if c.args[0] == "post"]
69+
self.assertEqual(len(post_calls), 1)
70+
payload = post_calls[0].kwargs.get("json", {})
71+
target = payload["Targets"][0]
72+
# accountnumber should only be in @odata.id, NOT as a body field
73+
self.assertNotIn("accountnumber", target)
74+
self.assertIn("name", target)
75+
self.assertIn("telephone1", target)
76+
self.assertIn("@odata.id", target)
77+
self.assertIn("accountnumber", target["@odata.id"])
78+
79+
def test_payload_excludes_alternate_key_even_when_in_record(self):
80+
"""If user passes matching key field in record, it should still be excluded from body."""
81+
self.od._upsert_multiple(
82+
"accounts",
83+
"account",
84+
[{"accountnumber": "ACC-001"}],
85+
[{"accountnumber": "ACC-001", "name": "Contoso"}],
86+
)
87+
post_calls = [c for c in self.od._request.call_args_list if c.args[0] == "post"]
88+
payload = post_calls[0].kwargs.get("json", {})
89+
target = payload["Targets"][0]
90+
# Even though user passed accountnumber in record with same value,
91+
# it should still appear in the body because it came from record_processed
92+
# (the conflict check allows matching values through)
93+
self.assertIn("@odata.id", target)
94+
self.assertIn("accountnumber", target["@odata.id"])
95+
6096
def test_record_conflicts_with_alternate_key_raises_value_error(self):
6197
"""_upsert_multiple raises ValueError when a record field contradicts its alternate key."""
6298
with self.assertRaises(ValueError) as ctx:

0 commit comments

Comments
 (0)