@@ -479,6 +479,80 @@ def test_direct_row_commit_with_unknown_exception():
479479 assert app_profile_id == call_args .app_profile_id [0 ]
480480
481481
482+ def _commit_with_operation_error (exc ):
483+ """Commit a row whose underlying ``mutate_row`` fails with ``exc``."""
484+ credentials = _make_credentials ()
485+ client = _make_client (project = "project-id" , credentials = credentials , admin = True )
486+ table = _Table ("projects/more-stuff" , client = client , app_profile_id = None )
487+ row = _make_direct_row (b"row_key" , table )
488+ row .set_cell ("column_family_id" , b"column" , b"bytes-value" )
489+
490+ with mock .patch .object (table ._table_impl , "mutate_row" ) as mutate_row_mock :
491+ mutate_row_mock .side_effect = exc
492+ return row .commit ()
493+
494+
495+ def test_direct_row_commit_unwraps_retry_exception_group ():
496+ """On timeout the data client raises a synthetic DeadlineExceeded chained
497+ with the errors seen on the wire. Report the terminal wire error, so that a
498+ single-row commit agrees with the equivalent bulk mutate_rows."""
499+ from google .api_core .exceptions import ServiceUnavailable
500+ from google .api_core .retry import RetryFailureReason
501+ from google .rpc import code_pb2
502+
503+ from google .cloud .bigtable .data ._helpers import _retry_exception_factory
504+
505+ source_exc , _ = _retry_exception_factory (
506+ [ServiceUnavailable ("first failure" ), ServiceUnavailable ("final failure" )],
507+ RetryFailureReason .TIMEOUT ,
508+ 60.0 ,
509+ )
510+
511+ result = _commit_with_operation_error (source_exc )
512+
513+ assert result .code == code_pb2 .Code .UNAVAILABLE
514+ assert result .message == "final failure"
515+
516+
517+ def test_direct_row_commit_reports_terminal_error_when_not_timed_out ():
518+ """When retries stop for any other reason the primary exception already is
519+ the terminal error and the chained group holds only earlier attempts, so it
520+ must not be unwrapped."""
521+ from google .api_core .exceptions import PermissionDenied , ServiceUnavailable
522+ from google .api_core .retry import RetryFailureReason
523+ from google .rpc import code_pb2
524+
525+ from google .cloud .bigtable .data ._helpers import _retry_exception_factory
526+
527+ source_exc , _ = _retry_exception_factory (
528+ [ServiceUnavailable ("earlier attempt" ), PermissionDenied ("terminal failure" )],
529+ RetryFailureReason .NON_RETRYABLE_ERROR ,
530+ 60.0 ,
531+ )
532+
533+ result = _commit_with_operation_error (source_exc )
534+
535+ assert result .code == code_pb2 .Code .PERMISSION_DENIED
536+ assert result .message == "terminal failure"
537+
538+
539+ def test_direct_row_commit_keeps_deadline_exceeded_returned_by_server ():
540+ """A DeadlineExceeded that came back from the server is the terminal error
541+ and is reported as-is, unlike the locally built one."""
542+ from google .api_core .exceptions import DeadlineExceeded , ServiceUnavailable
543+ from google .rpc import code_pb2
544+
545+ from google .cloud .bigtable .data .exceptions import RetryExceptionGroup
546+
547+ exc = DeadlineExceeded ("server deadline exceeded" , response = mock .Mock ())
548+ exc .__cause__ = RetryExceptionGroup ([ServiceUnavailable ("earlier attempt" )])
549+
550+ result = _commit_with_operation_error (exc )
551+
552+ assert result .code == code_pb2 .Code .DEADLINE_EXCEEDED
553+ assert result .message == "server deadline exceeded"
554+
555+
482556def test_direct_row_commit_with_invalid_argument ():
483557 from google .cloud .bigtable_v2 .services .bigtable import BigtableClient
484558
0 commit comments