@@ -543,7 +543,38 @@ async def test_read_row_failure_grpc(
543543
544544 No headers expected
545545 """
546- pass
546+ await temp_rows .add_row (b"row_key_1" )
547+ handler .clear ()
548+ exc = Aborted ("injected" )
549+ num_retryable = 2
550+ for i in range (num_retryable ):
551+ error_injector .push (exc )
552+ error_injector .push (PermissionDenied ("terminal" ))
553+ with pytest .raises (PermissionDenied ):
554+ await table .read_row (b"row_key_1" , retryable_errors = [Aborted ])
555+ # validate counts
556+ assert len (handler .completed_operations ) == 1
557+ assert len (handler .completed_attempts ) == num_retryable + 1
558+ assert len (handler .cancelled_operations ) == 0
559+ # validate operation
560+ operation = handler .completed_operations [0 ]
561+ assert isinstance (operation , CompletedOperationMetric )
562+ assert operation .final_status .name == "PERMISSION_DENIED"
563+ assert operation .op_type .value == "ReadRows"
564+ assert operation .is_streaming is False
565+ assert len (operation .completed_attempts ) == num_retryable + 1
566+ assert operation .cluster_id == "unspecified"
567+ assert operation .zone == "global"
568+ # validate attempts
569+ for i in range (num_retryable ):
570+ attempt = handler .completed_attempts [i ]
571+ assert isinstance (attempt , CompletedAttemptMetric )
572+ assert attempt .end_status .name == "ABORTED"
573+ assert attempt .gfe_latency_ns is None
574+ final_attempt = handler .completed_attempts [num_retryable ]
575+ assert isinstance (final_attempt , CompletedAttemptMetric )
576+ assert final_attempt .end_status .name == "PERMISSION_DENIED"
577+ assert final_attempt .gfe_latency_ns is None
547578
548579 @CrossSync .pytest
549580 async def test_read_row_failure_timeout (self , table , temp_rows , handler ):
@@ -552,7 +583,28 @@ async def test_read_row_failure_timeout(self, table, temp_rows, handler):
552583
553584 No grpc headers expected
554585 """
555- pass
586+ await temp_rows .add_row (b"row_key_1" )
587+ handler .clear ()
588+ with pytest .raises (GoogleAPICallError ):
589+ await table .read_row (b"row_key_1" , operation_timeout = 0.001 )
590+ # validate counts
591+ assert len (handler .completed_operations ) == 1
592+ assert len (handler .completed_attempts ) == 1
593+ assert len (handler .cancelled_operations ) == 0
594+ # validate operation
595+ operation = handler .completed_operations [0 ]
596+ assert isinstance (operation , CompletedOperationMetric )
597+ assert operation .final_status .name == "DEADLINE_EXCEEDED"
598+ assert operation .op_type .value == "ReadRows"
599+ assert operation .is_streaming is False
600+ assert len (operation .completed_attempts ) == 1
601+ assert operation .cluster_id == "unspecified"
602+ assert operation .zone == "global"
603+ # validate attempt
604+ attempt = handler .completed_attempts [0 ]
605+ assert isinstance (attempt , CompletedAttemptMetric )
606+ assert attempt .end_status .name == "DEADLINE_EXCEEDED"
607+ assert attempt .gfe_latency_ns is None
556608
557609 @CrossSync .pytest
558610 async def test_read_row_failure_unauthorized (
@@ -561,7 +613,29 @@ async def test_read_row_failure_unauthorized(
561613 """
562614 Test failure in backend by accessing an unauthorized family
563615 """
564- pass
616+ from google .cloud .bigtable .data .row_filters import FamilyNameRegexFilter
617+
618+ with pytest .raises (GoogleAPICallError ) as e :
619+ await authorized_view .read_row (b"any_row" , row_filter = FamilyNameRegexFilter ("unauthorized" ))
620+ assert e .value .grpc_status_code .name == "PERMISSION_DENIED"
621+ # validate counts
622+ assert len (handler .completed_operations ) == 1
623+ assert len (handler .completed_attempts ) == 1
624+ assert len (handler .cancelled_operations ) == 0
625+ # validate operation
626+ operation = handler .completed_operations [0 ]
627+ assert isinstance (operation , CompletedOperationMetric )
628+ assert operation .final_status .name == "PERMISSION_DENIED"
629+ assert operation .op_type .value == "ReadRows"
630+ assert operation .is_streaming is False
631+ assert len (operation .completed_attempts ) == 1
632+ assert operation .cluster_id == next (iter (cluster_config .keys ()))
633+ assert operation .zone == cluster_config [operation .cluster_id ].location .split ("/" )[- 1 ]
634+ # validate attempt
635+ attempt = handler .completed_attempts [0 ]
636+ assert isinstance (attempt , CompletedAttemptMetric )
637+ assert attempt .end_status .name == "PERMISSION_DENIED"
638+ assert attempt .gfe_latency_ns >= 0 and attempt .gfe_latency_ns < operation .duration_ns
565639
566640 @CrossSync .pytest
567641 async def test_read_rows_sharded (self , table , temp_rows , handler , cluster_config ):
@@ -611,7 +685,46 @@ async def test_read_rows_sharded_failure_grpc(
611685
612686 No headers expected
613687 """
614- pass
688+ from google .cloud .bigtable .data .read_rows_query import ReadRowsQuery
689+ from google .cloud .bigtable .data .exceptions import ShardedReadRowsExceptionGroup
690+
691+ await temp_rows .add_row (b"a" )
692+ await temp_rows .add_row (b"b" )
693+ query1 = ReadRowsQuery (row_keys = [b"a" ])
694+ query2 = ReadRowsQuery (row_keys = [b"b" ])
695+ handler .clear ()
696+
697+ error_injector .push (PermissionDenied ("terminal" ))
698+ with pytest .raises (ShardedReadRowsExceptionGroup ) as e :
699+ await table .read_rows_sharded ([query1 , query2 ])
700+ assert len (e .value .exceptions ) == 1
701+ assert isinstance (e .value .exceptions [0 ].__cause__ , PermissionDenied )
702+
703+ assert len (handler .completed_operations ) == 2
704+ assert len (handler .completed_attempts ) == 2
705+ assert len (handler .cancelled_operations ) == 0
706+ # sort operations by status
707+ failed_op = next (op for op in handler .completed_operations if op .final_status .name != "OK" )
708+ success_op = next (op for op in handler .completed_operations if op .final_status .name == "OK" )
709+ # validate failed operation
710+ assert failed_op .final_status .name == "PERMISSION_DENIED"
711+ assert failed_op .op_type .value == "ReadRows"
712+ assert failed_op .is_streaming is True
713+ assert len (failed_op .completed_attempts ) == 1
714+ assert failed_op .cluster_id == "unspecified"
715+ assert failed_op .zone == "global"
716+ # validate failed attempt
717+ failed_attempt = failed_op .completed_attempts [0 ]
718+ assert failed_attempt .end_status .name == "PERMISSION_DENIED"
719+ assert failed_attempt .gfe_latency_ns is None
720+ # validate successful operation
721+ assert success_op .final_status .name == "OK"
722+ assert success_op .op_type .value == "ReadRows"
723+ assert success_op .is_streaming is True
724+ assert len (success_op .completed_attempts ) == 1
725+ # validate successful attempt
726+ success_attempt = success_op .completed_attempts [0 ]
727+ assert success_attempt .end_status .name == "OK"
615728
616729 @CrossSync .pytest
617730 async def test_read_rows_sharded_failure_timeout (
@@ -622,7 +735,38 @@ async def test_read_rows_sharded_failure_timeout(
622735
623736 No grpc headers expected
624737 """
625- pass
738+ from google .cloud .bigtable .data .read_rows_query import ReadRowsQuery
739+ from google .cloud .bigtable .data .exceptions import ShardedReadRowsExceptionGroup
740+ from google .api_core .exceptions import DeadlineExceeded
741+
742+ await temp_rows .add_row (b"a" )
743+ await temp_rows .add_row (b"b" )
744+ query1 = ReadRowsQuery (row_keys = [b"a" ])
745+ query2 = ReadRowsQuery (row_keys = [b"b" ])
746+ handler .clear ()
747+ with pytest .raises (ShardedReadRowsExceptionGroup ) as e :
748+ await table .read_rows_sharded ([query1 , query2 ], operation_timeout = 0.005 )
749+ assert len (e .value .exceptions ) == 2
750+ for sub_exc in e .value .exceptions :
751+ assert isinstance (sub_exc .__cause__ , DeadlineExceeded )
752+ # both shards should fail
753+ assert len (handler .completed_operations ) == 2
754+ assert len (handler .completed_attempts ) == 2
755+ assert len (handler .cancelled_operations ) == 0
756+ # validate operations
757+ for operation in handler .completed_operations :
758+ assert isinstance (operation , CompletedOperationMetric )
759+ assert operation .final_status .name == "DEADLINE_EXCEEDED"
760+ assert operation .op_type .value == "ReadRows"
761+ assert operation .is_streaming is True
762+ assert len (operation .completed_attempts ) == 1
763+ assert operation .cluster_id == "unspecified"
764+ assert operation .zone == "global"
765+ # validate attempt
766+ attempt = operation .completed_attempts [0 ]
767+ assert isinstance (attempt , CompletedAttemptMetric )
768+ assert attempt .end_status .name == "DEADLINE_EXCEEDED"
769+ assert attempt .gfe_latency_ns is None
626770
627771 @CrossSync .pytest
628772 async def test_read_rows_sharded_failure_unauthorized (
@@ -631,7 +775,44 @@ async def test_read_rows_sharded_failure_unauthorized(
631775 """
632776 Test failure in backend by accessing an unauthorized family
633777 """
634- pass
778+ from google .cloud .bigtable .data .read_rows_query import ReadRowsQuery
779+ from google .cloud .bigtable .data .row_filters import FamilyNameRegexFilter
780+ from google .cloud .bigtable .data .exceptions import ShardedReadRowsExceptionGroup
781+
782+ query1 = ReadRowsQuery (row_filter = FamilyNameRegexFilter ("unauthorized" ))
783+ query2 = ReadRowsQuery (row_filter = FamilyNameRegexFilter (TEST_FAMILY ))
784+ handler .clear ()
785+ with pytest .raises (ShardedReadRowsExceptionGroup ) as e :
786+ await authorized_view .read_rows_sharded ([query1 , query2 ])
787+ assert len (e .value .exceptions ) == 1
788+ assert isinstance (e .value .exceptions [0 ].__cause__ , GoogleAPICallError )
789+ assert e .value .exceptions [0 ].__cause__ .grpc_status_code .name == "PERMISSION_DENIED"
790+ # one shard will fail, the other will succeed
791+ assert len (handler .completed_operations ) == 2
792+ assert len (handler .completed_attempts ) == 2
793+ assert len (handler .cancelled_operations ) == 0
794+ # sort operations by status
795+ failed_op = next (op for op in handler .completed_operations if op .final_status .name != "OK" )
796+ success_op = next (op for op in handler .completed_operations if op .final_status .name == "OK" )
797+ # validate failed operation
798+ assert failed_op .final_status .name == "PERMISSION_DENIED"
799+ assert failed_op .op_type .value == "ReadRows"
800+ assert failed_op .is_streaming is True
801+ assert len (failed_op .completed_attempts ) == 1
802+ assert failed_op .cluster_id == next (iter (cluster_config .keys ()))
803+ assert failed_op .zone == cluster_config [failed_op .cluster_id ].location .split ("/" )[- 1 ]
804+ # validate failed attempt
805+ failed_attempt = failed_op .completed_attempts [0 ]
806+ assert failed_attempt .end_status .name == "PERMISSION_DENIED"
807+ assert failed_attempt .gfe_latency_ns >= 0 and failed_attempt .gfe_latency_ns < failed_op .duration_ns
808+ # validate successful operation
809+ assert success_op .final_status .name == "OK"
810+ assert success_op .op_type .value == "ReadRows"
811+ assert success_op .is_streaming is True
812+ assert len (success_op .completed_attempts ) == 1
813+ # validate successful attempt
814+ success_attempt = success_op .completed_attempts [0 ]
815+ assert success_attempt .end_status .name == "OK"
635816
636817 @CrossSync .pytest
637818 async def test_read_rows_sharded_failure_mid_stream (
@@ -640,7 +821,32 @@ async def test_read_rows_sharded_failure_mid_stream(
640821 """
641822 Test failure in grpc stream
642823 """
643- pass
824+ from google .cloud .bigtable .data .read_rows_query import ReadRowsQuery
825+ from google .cloud .bigtable .data .exceptions import ShardedReadRowsExceptionGroup
826+
827+ await temp_rows .add_row (b"a" )
828+ await temp_rows .add_row (b"b" )
829+ query1 = ReadRowsQuery (row_keys = [b"a" ])
830+ query2 = ReadRowsQuery (row_keys = [b"b" ])
831+ handler .clear ()
832+ error_injector .fail_mid_stream = True
833+ error_injector .push (PermissionDenied ("terminal" ))
834+ error_injector .push (PermissionDenied ("terminal" ))
835+ with pytest .raises (ShardedReadRowsExceptionGroup ) as e :
836+ await table .read_rows_sharded ([query1 , query2 ])
837+ assert len (e .value .exceptions ) == 2
838+ assert isinstance (e .value .exceptions [0 ].__cause__ , PermissionDenied )
839+ assert len (handler .completed_operations ) == 2
840+ assert len (handler .completed_attempts ) == 2
841+ assert len (handler .cancelled_operations ) == 0
842+ for operation in handler .completed_operations :
843+ assert operation .final_status .name == "PERMISSION_DENIED"
844+ assert operation .op_type .value == "ReadRows"
845+ assert operation .is_streaming is True
846+ assert len (operation .completed_attempts ) == 1
847+ # validate attempt
848+ attempt = operation .completed_attempts [0 ]
849+ assert attempt .end_status .name == "PERMISSION_DENIED"
644850
645851 @CrossSync .pytest
646852 async def test_bulk_mutate_rows (self , table , temp_rows , handler , cluster_config ):
0 commit comments