1919 _TableList ,
2020 _QuerySql ,
2121 _extract_boundary ,
22+ _raise_top_level_batch_error ,
2223 _split_multipart ,
2324 _parse_mime_part ,
2425 _parse_http_response_part ,
2526 _CRLF ,
2627)
28+ from PowerPlatform .Dataverse .core .errors import HttpError
2729from PowerPlatform .Dataverse .models .upsert import UpsertItem
2830from PowerPlatform .Dataverse .data ._raw_request import _RawRequest
2931from PowerPlatform .Dataverse .models .batch import BatchItemResponse , BatchResult
@@ -430,18 +432,16 @@ def test_upsert_plain_dict_normalised_to_upsert_item(self):
430432
431433 def test_upsert_empty_list_raises (self ):
432434 from PowerPlatform .Dataverse .operations .batch import BatchRecordOperations
433- from PowerPlatform .Dataverse .core .errors import ValidationError
434435
435436 rec_ops , _ = self ._make_batch ()
436- with self .assertRaises (ValidationError ):
437+ with self .assertRaises (TypeError ):
437438 rec_ops .upsert ("account" , [])
438439
439440 def test_upsert_invalid_item_raises (self ):
440441 from PowerPlatform .Dataverse .operations .batch import BatchRecordOperations
441- from PowerPlatform .Dataverse .core .errors import ValidationError
442442
443443 rec_ops , _ = self ._make_batch ()
444- with self .assertRaises (ValidationError ):
444+ with self .assertRaises (TypeError ):
445445 rec_ops .upsert ("account" , ["not_a_valid_item" ])
446446
447447 def test_upsert_multiple_items_all_normalised (self ):
@@ -461,5 +461,65 @@ def test_upsert_multiple_items_all_normalised(self):
461461 self .assertEqual (intent .items [1 ].alternate_key , {"accountnumber" : "B" })
462462
463463
464+ class TestRaiseTopLevelBatchError (unittest .TestCase ):
465+ """_raise_top_level_batch_error surfaces Dataverse error details as HttpError."""
466+
467+ def _make_response (self , status_code , json_body = None , text = None ):
468+ resp = MagicMock ()
469+ resp .status_code = status_code
470+ resp .text = text or ""
471+ if json_body is not None :
472+ resp .json .return_value = json_body
473+ else :
474+ resp .json .side_effect = ValueError ("no JSON" )
475+ return resp
476+
477+ def test_raises_http_error (self ):
478+ """Always raises HttpError, never returns."""
479+ resp = self ._make_response (400 , json_body = {"error" : {"code" : "0x0" , "message" : "Bad batch" }})
480+ with self .assertRaises (HttpError ):
481+ _raise_top_level_batch_error (resp )
482+
483+ def test_status_code_preserved (self ):
484+ """HttpError.status_code matches the response status code."""
485+ resp = self ._make_response (400 , json_body = {"error" : {"code" : "0x0" , "message" : "Bad batch" }})
486+ with self .assertRaises (HttpError ) as ctx :
487+ _raise_top_level_batch_error (resp )
488+ self .assertEqual (ctx .exception .status_code , 400 )
489+
490+ def test_service_message_in_exception (self ):
491+ """The Dataverse error message is included in the raised exception."""
492+ resp = self ._make_response (400 , json_body = {"error" : {"code" : "BadRequest" , "message" : "Malformed OData batch" }})
493+ with self .assertRaises (HttpError ) as ctx :
494+ _raise_top_level_batch_error (resp )
495+ self .assertIn ("Malformed OData batch" , str (ctx .exception ))
496+
497+ def test_service_error_code_preserved (self ):
498+ """The Dataverse error code is forwarded into HttpError.details."""
499+ resp = self ._make_response (400 , json_body = {"error" : {"code" : "0x80040216" , "message" : "..." }})
500+ with self .assertRaises (HttpError ) as ctx :
501+ _raise_top_level_batch_error (resp )
502+ self .assertEqual (ctx .exception .details .get ("service_error_code" ), "0x80040216" )
503+
504+ def test_falls_back_to_response_text_when_no_json (self ):
505+ """Falls back to response.text when the body is not valid JSON."""
506+ resp = self ._make_response (400 , text = "plain text error body" )
507+ with self .assertRaises (HttpError ) as ctx :
508+ _raise_top_level_batch_error (resp )
509+ self .assertIn ("plain text error body" , str (ctx .exception ))
510+
511+ def test_parse_batch_response_raises_on_missing_boundary (self ):
512+ """_BatchClient._parse_batch_response raises HttpError for non-multipart responses."""
513+ od = _make_od ()
514+ client = _BatchClient (od )
515+ resp = MagicMock ()
516+ resp .headers = {"Content-Type" : "application/json" }
517+ resp .status_code = 400
518+ resp .text = ""
519+ resp .json .return_value = {"error" : {"code" : "0x0" , "message" : "Invalid batch" }}
520+ with self .assertRaises (HttpError ):
521+ client ._parse_batch_response (resp )
522+
523+
464524if __name__ == "__main__" :
465525 unittest .main ()
0 commit comments