@@ -307,6 +307,49 @@ def test_update_clear_nulls_false(self):
307307 self .assertIn ("name" , changes )
308308 self .assertNotIn ("telephone1" , changes )
309309
310+ def test_update_all_nan_rows_skipped (self ):
311+ """When all change values are NaN for every row, no API call is made."""
312+ df = pd .DataFrame (
313+ [
314+ {"accountid" : "guid-1" , "telephone1" : None , "websiteurl" : None },
315+ {"accountid" : "guid-2" , "telephone1" : None , "websiteurl" : None },
316+ ]
317+ )
318+ self .client .dataframe .update ("account" , df , id_column = "accountid" )
319+ self .client ._odata ._update .assert_not_called ()
320+ self .client ._odata ._update_by_ids .assert_not_called ()
321+
322+ def test_update_partial_nan_rows_filtered (self ):
323+ """Rows where all changes are NaN are filtered; remaining rows proceed."""
324+ df = pd .DataFrame (
325+ [
326+ {"accountid" : "guid-1" , "name" : "Updated" , "telephone1" : None },
327+ {"accountid" : "guid-2" , "name" : None , "telephone1" : None },
328+ ]
329+ )
330+ self .client .dataframe .update ("account" , df , id_column = "accountid" )
331+ self .client ._odata ._update .assert_called_once_with ("account" , "guid-1" , {"name" : "Updated" })
332+
333+ def test_update_invalid_ids_reports_index_labels (self ):
334+ """Error message reports DataFrame index labels, not positional indices."""
335+ df = pd .DataFrame (
336+ [
337+ {"accountid" : "guid-1" , "name" : "A" },
338+ {"accountid" : None , "name" : "B" },
339+ ],
340+ index = ["row_a" , "row_b" ],
341+ )
342+ with self .assertRaises (ValueError ) as ctx :
343+ self .client .dataframe .update ("account" , df , id_column = "accountid" )
344+ self .assertIn ("row_b" , str (ctx .exception ))
345+
346+ def test_update_strips_whitespace_from_ids (self ):
347+ """Leading/trailing whitespace in IDs is stripped before API call."""
348+ df = pd .DataFrame ([{"accountid" : " guid-1 " , "name" : "Contoso" }])
349+ self .client .dataframe .update ("account" , df , id_column = "accountid" )
350+ call_args = self .client ._odata ._update .call_args [0 ]
351+ self .assertEqual (call_args [1 ], "guid-1" )
352+
310353 def test_update_clear_nulls_true (self ):
311354 """NaN values are sent as None in the update payload when clear_nulls=True."""
312355 df = pd .DataFrame ([{"accountid" : "guid-1" , "name" : "New Name" , "telephone1" : None }])
@@ -368,6 +411,19 @@ def test_delete_with_bulk_delete_false(self):
368411 self .assertIsNone (result )
369412 self .assertEqual (self .client ._odata ._delete .call_count , 2 )
370413
414+ def test_delete_invalid_ids_reports_index_labels (self ):
415+ """Error message reports Series index labels, not positional indices."""
416+ ids = pd .Series (["guid-1" , None ], index = ["row_x" , "row_y" ])
417+ with self .assertRaises (ValueError ) as ctx :
418+ self .client .dataframe .delete ("account" , ids )
419+ self .assertIn ("row_y" , str (ctx .exception ))
420+
421+ def test_delete_strips_whitespace_from_ids (self ):
422+ """Leading/trailing whitespace in IDs is stripped before API call."""
423+ ids = pd .Series ([" guid-1 " ])
424+ self .client .dataframe .delete ("account" , ids )
425+ self .client ._odata ._delete .assert_called_once_with ("account" , "guid-1" )
426+
371427
372428class TestDataFrameEndToEnd (unittest .TestCase ):
373429 """End-to-end mocked flow: create -> get -> update -> delete."""
0 commit comments