Skip to content

Commit caba604

Browse files
Abel Milashclaude
andcommitted
Improve test_odata_internal: rename r vars, add multi-record tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6187c63 commit caba604

1 file changed

Lines changed: 88 additions & 61 deletions

File tree

tests/unit/data/test_odata_internal.py

Lines changed: 88 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -605,33 +605,33 @@ def setUp(self):
605605
self.client = _ODataClient(mock_auth, "https://example.crm.dynamics.com")
606606

607607
def _make_raw_response(self, status_code, json_data=None, headers=None):
608-
r = MagicMock()
609-
r.status_code = status_code
610-
r.text = "body"
611-
r.json.return_value = json_data or {}
612-
r.headers = headers or {}
613-
return r
608+
response = MagicMock()
609+
response.status_code = status_code
610+
response.text = "body"
611+
response.json.return_value = json_data or {}
612+
response.headers = headers or {}
613+
return response
614614

615615
def test_message_key_fallback_used_when_no_error_key(self):
616616
"""_request uses 'message' key when 'error' key is absent."""
617-
r = self._make_raw_response(400, json_data={"message": "Bad input received"})
618-
self.client._raw_request = MagicMock(return_value=r)
617+
response = self._make_raw_response(400, json_data={"message": "Bad input received"})
618+
self.client._raw_request = MagicMock(return_value=response)
619619
with self.assertRaises(HttpError) as ctx:
620620
self.client._request("get", "http://example.com/test")
621621
self.assertIn("Bad input received", str(ctx.exception))
622622

623623
def test_retry_after_non_int_not_stored_in_details(self):
624624
"""Retry-After header that is non-numeric results in retry_after absent from details."""
625-
r = self._make_raw_response(429, headers={"Retry-After": "not-a-number"})
626-
self.client._raw_request = MagicMock(return_value=r)
625+
response = self._make_raw_response(429, headers={"Retry-After": "not-a-number"})
626+
self.client._raw_request = MagicMock(return_value=response)
627627
with self.assertRaises(HttpError) as ctx:
628628
self.client._request("get", "http://example.com/test")
629629
self.assertIsNone(ctx.exception.details.get("retry_after"))
630630

631631
def test_retry_after_int_stored_in_details(self):
632632
"""Retry-After header that is numeric is stored in exception details."""
633-
r = self._make_raw_response(429, headers={"Retry-After": "30"})
634-
self.client._raw_request = MagicMock(return_value=r)
633+
response = self._make_raw_response(429, headers={"Retry-After": "30"})
634+
self.client._raw_request = MagicMock(return_value=response)
635635
with self.assertRaises(HttpError) as ctx:
636636
self.client._request("get", "http://example.com/test")
637637
self.assertEqual(ctx.exception.details.get("retry_after"), 30)
@@ -665,49 +665,62 @@ def test_odata_type_already_present_not_duplicated(self):
665665

666666
def test_body_not_dict_returns_empty_list(self):
667667
"""When response body is not a dict, returns empty list."""
668-
r = _mock_response(text='["id1", "id2"]')
669-
r.json.return_value = ["id1", "id2"]
670-
self.od._request.return_value = r
668+
response = _mock_response(text='["id1", "id2"]')
669+
response.json.return_value = ["id1", "id2"]
670+
self.od._request.return_value = response
671671
result = self.od._create_multiple("accounts", "account", [{"name": "A"}])
672672
self.assertEqual(result, [])
673673

674674
def test_value_key_path_extracts_ids(self):
675675
"""Falls back to 'value' key to extract IDs via heuristic."""
676676
long_guid = "a" * 32
677-
r = _mock_response(
677+
response = _mock_response(
678678
json_data={"value": [{"accountid": long_guid, "name": "Test"}]},
679679
text="...",
680680
)
681-
self.od._request.return_value = r
681+
self.od._request.return_value = response
682682
result = self.od._create_multiple("accounts", "account", [{"name": "Test"}])
683683
self.assertEqual(result, [long_guid])
684684

685685
def test_value_key_with_non_dict_items_returns_empty(self):
686686
"""'value' list with non-dict items returns empty list."""
687-
r = _mock_response(json_data={"value": ["not-a-dict"]}, text="...")
688-
self.od._request.return_value = r
687+
response = _mock_response(json_data={"value": ["not-a-dict"]}, text="...")
688+
self.od._request.return_value = response
689689
self.od._convert_labels_to_ints = MagicMock(side_effect=lambda _, rec: rec)
690690
result = self.od._create_multiple("accounts", "account", [{"name": "Test"}])
691691
self.assertEqual(result, [])
692692

693693
def test_no_ids_or_value_key_returns_empty_list(self):
694694
"""When body has neither 'Ids' nor 'value' keys, returns empty list."""
695-
r = _mock_response(json_data={"something_else": "data"}, text="...")
696-
self.od._request.return_value = r
695+
response = _mock_response(json_data={"something_else": "data"}, text="...")
696+
self.od._request.return_value = response
697697
self.od._convert_labels_to_ints = MagicMock(side_effect=lambda _, rec: rec)
698698
result = self.od._create_multiple("accounts", "account", [{"name": "Test"}])
699699
self.assertEqual(result, [])
700700

701701
def test_value_parse_error_returns_empty_list(self):
702702
"""ValueError in body.json() returns empty list."""
703-
r = MagicMock()
704-
r.text = "invalid json"
705-
r.json.side_effect = ValueError("bad json")
706-
self.od._request.return_value = r
703+
response = MagicMock()
704+
response.text = "invalid json"
705+
response.json.side_effect = ValueError("bad json")
706+
self.od._request.return_value = response
707707
self.od._convert_labels_to_ints = MagicMock(side_effect=lambda _, rec: rec)
708708
result = self.od._create_multiple("accounts", "account", [{"name": "Test"}])
709709
self.assertEqual(result, [])
710710

711+
def test_multiple_records_returns_all_ids(self):
712+
"""All IDs from the Ids response key are returned for multiple input records."""
713+
self.od._request.return_value = _mock_response(
714+
json_data={"Ids": ["id-1", "id-2", "id-3"]},
715+
text='{"Ids": ["id-1", "id-2", "id-3"]}',
716+
)
717+
result = self.od._create_multiple(
718+
"accounts",
719+
"account",
720+
[{"name": "A"}, {"name": "B"}, {"name": "C"}],
721+
)
722+
self.assertEqual(result, ["id-1", "id-2", "id-3"])
723+
711724

712725
class TestPrimaryIdAttr(unittest.TestCase):
713726
"""Unit tests for _ODataClient._primary_id_attr cache-miss behavior."""
@@ -857,6 +870,20 @@ def test_payload_contains_targets_array(self):
857870
self.assertEqual(len(payload["Targets"]), 1)
858871
self.assertIn("@odata.type", payload["Targets"][0])
859872

873+
def test_multiple_records_all_in_targets(self):
874+
"""All records are included in the Targets payload for multiple inputs."""
875+
self.od._request.return_value = _mock_response()
876+
records = [
877+
{"accountid": "id-1", "name": "A"},
878+
{"accountid": "id-2", "name": "B"},
879+
{"accountid": "id-3", "name": "C"},
880+
]
881+
self.od._update_multiple("accounts", "account", records)
882+
payload = json.loads(self.od._request.call_args.kwargs["data"])
883+
self.assertEqual(len(payload["Targets"]), 3)
884+
self.assertEqual(payload["Targets"][0]["accountid"], "id-1")
885+
self.assertEqual(payload["Targets"][2]["accountid"], "id-3")
886+
860887

861888
class TestDeleteMultiple(unittest.TestCase):
862889
"""Unit tests for _ODataClient._delete_multiple."""
@@ -905,10 +932,10 @@ def test_returns_none_when_no_job_id_in_body(self):
905932

906933
def test_handles_value_error_in_json_parsing(self):
907934
"""_delete_multiple handles ValueError in response JSON parsing gracefully."""
908-
r = MagicMock()
909-
r.text = "invalid"
910-
r.json.side_effect = ValueError
911-
self.od._request.return_value = r
935+
response = MagicMock()
936+
response.text = "invalid"
937+
response.json.side_effect = ValueError
938+
self.od._request.return_value = response
912939
result = self.od._delete_multiple("account", ["id-1"])
913940
self.assertIsNone(result)
914941

@@ -944,8 +971,8 @@ def setUp(self):
944971

945972
def _single_page_response(self, items=None):
946973
data = {"value": items or [{"accountid": "id-1"}]}
947-
r = _mock_response(json_data=data, text=str(data))
948-
self.od._request.return_value = r
974+
response = _mock_response(json_data=data, text=str(data))
975+
self.od._request.return_value = response
949976

950977
def test_filter_param_passed(self):
951978
"""_get_multiple passes $filter to params."""
@@ -999,10 +1026,10 @@ def test_page_size_sets_prefer_header(self):
9991026

10001027
def test_value_error_in_json_returns_empty(self):
10011028
"""ValueError in page JSON parsing yields nothing."""
1002-
r = MagicMock()
1003-
r.text = "bad json"
1004-
r.json.side_effect = ValueError
1005-
self.od._request.return_value = r
1029+
response = MagicMock()
1030+
response.text = "bad json"
1031+
response.json.side_effect = ValueError
1032+
self.od._request.return_value = response
10061033
pages = list(self.od._get_multiple("account"))
10071034
self.assertEqual(pages, [])
10081035

@@ -1043,8 +1070,8 @@ def test_stops_when_no_nextlink(self):
10431070
def test_filters_non_dict_items_from_page(self):
10441071
"""_get_multiple filters out non-dict items from each page."""
10451072
data = {"value": [{"accountid": "id-1"}, "not-a-dict", 42]}
1046-
r = _mock_response(json_data=data, text=str(data))
1047-
self.od._request.return_value = r
1073+
response = _mock_response(json_data=data, text=str(data))
1074+
self.od._request.return_value = response
10481075
pages = list(self.od._get_multiple("account"))
10491076
self.assertEqual(len(pages), 1)
10501077
self.assertEqual(len(pages[0]), 1)
@@ -1053,8 +1080,8 @@ def test_filters_non_dict_items_from_page(self):
10531080
def test_empty_value_list_yields_nothing(self):
10541081
"""_get_multiple yields nothing when value list is empty."""
10551082
data = {"value": []}
1056-
r = _mock_response(json_data=data, text=str(data))
1057-
self.od._request.return_value = r
1083+
response = _mock_response(json_data=data, text=str(data))
1084+
self.od._request.return_value = response
10581085
pages = list(self.od._get_multiple("account"))
10591086
self.assertEqual(pages, [])
10601087

@@ -1096,26 +1123,26 @@ def test_filters_non_dict_rows(self):
10961123

10971124
def test_body_as_list_fallback(self):
10981125
"""_query_sql handles body being a list directly."""
1099-
r = _mock_response(text="...")
1100-
r.json.return_value = [{"name": "A"}, {"name": "B"}]
1101-
self.od._request.return_value = r
1126+
response = _mock_response(text="...")
1127+
response.json.return_value = [{"name": "A"}, {"name": "B"}]
1128+
self.od._request.return_value = response
11021129
result = self.od._query_sql("SELECT name FROM account")
11031130
self.assertEqual(len(result), 2)
11041131

11051132
def test_value_error_in_json_returns_empty(self):
11061133
"""_query_sql returns empty list when JSON parsing fails."""
1107-
r = MagicMock()
1108-
r.text = "bad json"
1109-
r.json.side_effect = ValueError
1110-
self.od._request.return_value = r
1134+
response = MagicMock()
1135+
response.text = "bad json"
1136+
response.json.side_effect = ValueError
1137+
self.od._request.return_value = response
11111138
result = self.od._query_sql("SELECT name FROM account")
11121139
self.assertEqual(result, [])
11131140

11141141
def test_unexpected_body_returns_empty(self):
11151142
"""_query_sql returns empty list for non-dict, non-list body."""
1116-
r = _mock_response(text="...")
1117-
r.json.return_value = "unexpected"
1118-
self.od._request.return_value = r
1143+
response = _mock_response(text="...")
1144+
response.json.return_value = "unexpected"
1145+
self.od._request.return_value = response
11191146
result = self.od._query_sql("SELECT name FROM account")
11201147
self.assertEqual(result, [])
11211148

@@ -1145,10 +1172,10 @@ def test_empty_table_schema_name_raises_value_error(self):
11451172

11461173
def test_json_value_error_in_response_treated_as_empty(self):
11471174
"""_entity_set_from_schema_name handles ValueError in JSON parsing."""
1148-
r = MagicMock()
1149-
r.text = "invalid json"
1150-
r.json.side_effect = ValueError
1151-
self.od._request.return_value = r
1175+
response = MagicMock()
1176+
response.text = "invalid json"
1177+
response.json.side_effect = ValueError
1178+
self.od._request.return_value = response
11521179
with self.assertRaises(MetadataError):
11531180
self.od._entity_set_from_schema_name("account")
11541181

@@ -1331,10 +1358,10 @@ def test_extra_select_skips_odata_annotation_pieces(self):
13311358

13321359
def test_value_error_in_json_returns_none(self):
13331360
"""_get_attribute_metadata returns None on JSON parse failure."""
1334-
r = MagicMock()
1335-
r.text = "bad json"
1336-
r.json.side_effect = ValueError
1337-
self.od._request.return_value = r
1361+
response = MagicMock()
1362+
response.text = "bad json"
1363+
response.json.side_effect = ValueError
1364+
self.od._request.return_value = response
13381365
result = self.od._get_attribute_metadata("meta-001", "name")
13391366
self.assertIsNone(result)
13401367

@@ -2374,11 +2401,11 @@ def test_convert_different_tables_separate_fetches(self):
23742401
resp2 = self._bulk_response(("new_status", [(100, "Open")]))
23752402
self.od._request.side_effect = [resp1, resp2]
23762403

2377-
r1 = self.od._convert_labels_to_ints("account", {"industrycode": "Tech"})
2378-
r2 = self.od._convert_labels_to_ints("new_ticket", {"new_status": "Open"})
2404+
result1 = self.od._convert_labels_to_ints("account", {"industrycode": "Tech"})
2405+
result2 = self.od._convert_labels_to_ints("new_ticket", {"new_status": "Open"})
23792406

2380-
self.assertEqual(r1["industrycode"], 6)
2381-
self.assertEqual(r2["new_status"], 100)
2407+
self.assertEqual(result1["industrycode"], 6)
2408+
self.assertEqual(result2["new_status"], 100)
23822409
self.assertEqual(self.od._request.call_count, 2)
23832410

23842411
def test_convert_only_odata_and_non_strings_skips_fetch(self):

0 commit comments

Comments
 (0)