|
12 | 12 |
|
13 | 13 | from PowerPlatform.Dataverse.client import DataverseClient |
14 | 14 | from PowerPlatform.Dataverse.operations.dataframe import DataFrameOperations |
15 | | -from PowerPlatform.Dataverse.utils._pandas import dataframe_to_records |
16 | | - |
17 | | - |
18 | | -class TestDataframeToRecordsHelper(unittest.TestCase): |
19 | | - """Unit tests for the dataframe_to_records() helper in isolation.""" |
20 | | - |
21 | | - def test_dataframe_to_records_basic(self): |
22 | | - """Basic DataFrame with string values is converted correctly.""" |
23 | | - df = pd.DataFrame([{"name": "Contoso", "city": "Seattle"}]) |
24 | | - result = dataframe_to_records(df) |
25 | | - self.assertEqual(result, [{"name": "Contoso", "city": "Seattle"}]) |
26 | | - |
27 | | - def test_dataframe_to_records_nan_dropped(self): |
28 | | - """NaN values are omitted from records when na_as_null=False (default).""" |
29 | | - df = pd.DataFrame([{"name": "Contoso", "telephone1": None}]) |
30 | | - result = dataframe_to_records(df) |
31 | | - self.assertNotIn("telephone1", result[0]) |
32 | | - |
33 | | - def test_dataframe_to_records_nan_as_null(self): |
34 | | - """NaN values become None when na_as_null=True.""" |
35 | | - df = pd.DataFrame([{"name": "Contoso", "telephone1": None}]) |
36 | | - result = dataframe_to_records(df, na_as_null=True) |
37 | | - self.assertIn("telephone1", result[0]) |
38 | | - self.assertIsNone(result[0]["telephone1"]) |
39 | | - |
40 | | - def test_dataframe_to_records_timestamp_conversion(self): |
41 | | - """pd.Timestamp values are converted to ISO 8601 strings.""" |
42 | | - ts = pd.Timestamp("2024-01-15 10:30:00") |
43 | | - df = pd.DataFrame([{"createdon": ts}]) |
44 | | - result = dataframe_to_records(df) |
45 | | - self.assertEqual(result[0]["createdon"], "2024-01-15T10:30:00") |
46 | | - |
47 | | - def test_dataframe_to_records_numpy_int(self): |
48 | | - """np.int64 values are converted to Python int.""" |
49 | | - df = pd.DataFrame([{"priority": np.int64(42)}]) |
50 | | - result = dataframe_to_records(df) |
51 | | - self.assertIsInstance(result[0]["priority"], int) |
52 | | - self.assertEqual(result[0]["priority"], 42) |
53 | | - |
54 | | - def test_dataframe_to_records_numpy_float(self): |
55 | | - """np.float64 values are converted to Python float.""" |
56 | | - df = pd.DataFrame([{"score": np.float64(3.14)}]) |
57 | | - result = dataframe_to_records(df) |
58 | | - self.assertIsInstance(result[0]["score"], float) |
59 | | - self.assertAlmostEqual(result[0]["score"], 3.14) |
60 | | - |
61 | | - def test_dataframe_to_records_numpy_bool(self): |
62 | | - """np.bool_ values are converted to Python bool.""" |
63 | | - df = pd.DataFrame([{"active": np.bool_(True)}]) |
64 | | - result = dataframe_to_records(df) |
65 | | - self.assertIsInstance(result[0]["active"], bool) |
66 | | - self.assertTrue(result[0]["active"]) |
67 | | - |
68 | | - def test_dataframe_to_records_list_value(self): |
69 | | - """Cells containing lists pass through without crashing.""" |
70 | | - df = pd.DataFrame([{"tags": ["a", "b", "c"]}]) |
71 | | - result = dataframe_to_records(df) |
72 | | - self.assertEqual(result[0]["tags"], ["a", "b", "c"]) |
73 | | - |
74 | | - def test_dataframe_to_records_dict_value(self): |
75 | | - """Cells containing dicts pass through without crashing.""" |
76 | | - df = pd.DataFrame([{"metadata": {"key": "value"}}]) |
77 | | - result = dataframe_to_records(df) |
78 | | - self.assertEqual(result[0]["metadata"], {"key": "value"}) |
79 | | - |
80 | | - def test_dataframe_to_records_empty_dataframe(self): |
81 | | - """Empty DataFrame returns an empty list.""" |
82 | | - df = pd.DataFrame(columns=["name", "telephone1"]) |
83 | | - result = dataframe_to_records(df) |
84 | | - self.assertEqual(result, []) |
85 | | - |
86 | | - def test_dataframe_to_records_mixed_types(self): |
87 | | - """DataFrame with mixed types converts all values correctly.""" |
88 | | - ts = pd.Timestamp("2024-06-01") |
89 | | - df = pd.DataFrame( |
90 | | - [ |
91 | | - { |
92 | | - "name": "Contoso", |
93 | | - "count": np.int64(5), |
94 | | - "score": np.float64(9.8), |
95 | | - "active": np.bool_(True), |
96 | | - "createdon": ts, |
97 | | - "notes": None, |
98 | | - } |
99 | | - ] |
100 | | - ) |
101 | | - result = dataframe_to_records(df) |
102 | | - rec = result[0] |
103 | | - self.assertEqual(rec["name"], "Contoso") |
104 | | - self.assertIsInstance(rec["count"], int) |
105 | | - self.assertIsInstance(rec["score"], float) |
106 | | - self.assertIsInstance(rec["active"], bool) |
107 | | - self.assertEqual(rec["createdon"], "2024-06-01T00:00:00") |
108 | | - self.assertNotIn("notes", rec) |
109 | 15 |
|
110 | 16 |
|
111 | 17 | class TestDataFrameOperationsNamespace(unittest.TestCase): |
@@ -187,6 +93,18 @@ def test_get_record_id_with_top_raises(self): |
187 | 93 | self.client.dataframe.get("account", record_id="guid-1", top=10) |
188 | 94 | self.assertIn("Cannot specify query parameters", str(ctx.exception)) |
189 | 95 |
|
| 96 | + def test_get_empty_record_id_raises(self): |
| 97 | + """ValueError raised when record_id is empty or whitespace.""" |
| 98 | + with self.assertRaises(ValueError) as ctx: |
| 99 | + self.client.dataframe.get("account", record_id=" ") |
| 100 | + self.assertIn("non-empty string", str(ctx.exception)) |
| 101 | + |
| 102 | + def test_get_record_id_stripped(self): |
| 103 | + """Leading/trailing whitespace in record_id is stripped.""" |
| 104 | + self.client._odata._get.return_value = {"accountid": "guid-1", "name": "Contoso"} |
| 105 | + self.client.dataframe.get("account", record_id=" guid-1 ") |
| 106 | + self.client._odata._get.assert_called_once_with("account", "guid-1", select=None) |
| 107 | + |
190 | 108 |
|
191 | 109 | class TestDataFrameCreate(unittest.TestCase): |
192 | 110 | """Tests for client.dataframe.create().""" |
|
0 commit comments