@@ -341,5 +341,87 @@ def test_returns_none(self):
341341 self .assertIsNone (result )
342342
343343
344+ class TestBuildUpsertMultiple (unittest .TestCase ):
345+ """Unit tests for _ODataClient._build_upsert_multiple (batch deferred build)."""
346+
347+ def setUp (self ):
348+ self .od = _make_odata_client ()
349+
350+ def _targets (self , alt_keys , records ):
351+ import json
352+
353+ req = self .od ._build_upsert_multiple ("accounts" , "account" , alt_keys , records )
354+ return json .loads (req .body )["Targets" ]
355+
356+ def test_payload_excludes_alternate_key_fields (self ):
357+ """Alternate key fields must NOT appear in the request body (only in @odata.id)."""
358+ targets = self ._targets (
359+ [{"accountnumber" : "ACC-001" }],
360+ [{"name" : "Contoso" }],
361+ )
362+ self .assertEqual (len (targets ), 1 )
363+ target = targets [0 ]
364+ self .assertNotIn ("accountnumber" , target )
365+ self .assertIn ("name" , target )
366+ self .assertIn ("@odata.id" , target )
367+ self .assertIn ("accountnumber" , target ["@odata.id" ])
368+
369+ def test_payload_allows_matching_key_field_in_record (self ):
370+ """If user passes matching key field in record with same value, it passes through to body."""
371+ targets = self ._targets (
372+ [{"accountnumber" : "ACC-001" }],
373+ [{"accountnumber" : "ACC-001" , "name" : "Contoso" }],
374+ )
375+ target = targets [0 ]
376+ self .assertIn ("name" , target )
377+ self .assertIn ("@odata.id" , target )
378+ self .assertIn ("accountnumber" , target ["@odata.id" ])
379+
380+ def test_odata_type_added_when_absent (self ):
381+ """@odata.type is injected when not provided by caller."""
382+ targets = self ._targets (
383+ [{"accountnumber" : "ACC-001" }],
384+ [{"name" : "Contoso" }],
385+ )
386+ self .assertIn ("@odata.type" , targets [0 ])
387+ self .assertEqual (targets [0 ]["@odata.type" ], "Microsoft.Dynamics.CRM.account" )
388+
389+ def test_multiple_targets_all_have_odata_id (self ):
390+ """Each target in a multi-item call gets its own @odata.id."""
391+ targets = self ._targets (
392+ [{"accountnumber" : "ACC-001" }, {"accountnumber" : "ACC-002" }],
393+ [{"name" : "Contoso" }, {"name" : "Fabrikam" }],
394+ )
395+ self .assertEqual (len (targets ), 2 )
396+ self .assertIn ("ACC-001" , targets [0 ]["@odata.id" ])
397+ self .assertIn ("ACC-002" , targets [1 ]["@odata.id" ])
398+
399+ def test_conflicting_key_field_raises (self ):
400+ """Raises when a record field contradicts its alternate key value."""
401+ with self .assertRaises (Exception ) as ctx :
402+ self .od ._build_upsert_multiple (
403+ "accounts" ,
404+ "account" ,
405+ [{"accountnumber" : "ACC-001" }],
406+ [{"accountnumber" : "ACC-WRONG" , "name" : "Contoso" }],
407+ )
408+ self .assertIn ("accountnumber" , str (ctx .exception ))
409+
410+ def test_mismatched_lengths_raises (self ):
411+ """Raises when alternate_keys and records lengths differ."""
412+ with self .assertRaises (Exception ):
413+ self .od ._build_upsert_multiple (
414+ "accounts" , "account" , [{"accountnumber" : "ACC-001" }], []
415+ )
416+
417+ def test_url_contains_upsert_multiple_action (self ):
418+ """POST URL targets the UpsertMultiple bound action."""
419+ req = self .od ._build_upsert_multiple (
420+ "accounts" , "account" , [{"accountnumber" : "ACC-001" }], [{"name" : "Contoso" }]
421+ )
422+ self .assertIn ("UpsertMultiple" , req .url )
423+ self .assertEqual (req .method , "POST" )
424+
425+
344426if __name__ == "__main__" :
345427 unittest .main ()
0 commit comments