@@ -1048,4 +1048,77 @@ async def test_bedrock_guardrail_parameter_takes_precedence_over_env(monkeypatch
1048
1048
prepped_request .url == expected_url
1049
1049
), f"Expected parameter endpoint to take precedence. Got: { prepped_request .url } "
1050
1050
1051
- print (f"Parameter precedence test passed. URL: { prepped_request .url } " )
1051
+ print (f"Parameter precedence test passed. URL: { prepped_request .url } " )
1052
+
1053
+ @pytest .mark .asyncio
1054
+ async def test_bedrock_guardrail_200_with_exception_in_output_raises_and_logs_failure ():
1055
+ """
1056
+ When Bedrock returns HTTP 200 but the body contains Output.__type with 'Exception',
1057
+ the guardrail should:
1058
+ - raise an HTTPException(400) with the Output payload in detail
1059
+ - log the request trace with guardrail_status='failure'
1060
+ """
1061
+ guardrail = BedrockGuardrail (
1062
+ guardrailIdentifier = "test-guardrail" , guardrailVersion = "DRAFT"
1063
+ )
1064
+
1065
+ # Mock a Bedrock "success" HTTP status but an Exception embedded in the body
1066
+ payload = {
1067
+ "Output" : {
1068
+ "__type" : "com.amazonaws#InternalServerException" ,
1069
+ "message" : "Something went wrong upstream" ,
1070
+ },
1071
+ "action" : "NONE" ,
1072
+ }
1073
+ mock_resp = MagicMock ()
1074
+ mock_resp .status_code = 200
1075
+ mock_resp .content = json .dumps (payload ).encode ("utf-8" )
1076
+ mock_resp .text = json .dumps (payload )
1077
+ mock_resp .json .return_value = payload
1078
+
1079
+ # Minimal request data
1080
+ request_data = {
1081
+ "model" : "gpt-4o" ,
1082
+ "messages" : [{"role" : "user" , "content" : "hello" }],
1083
+ }
1084
+
1085
+ # Mock creds and request prep
1086
+ mock_credentials = MagicMock ()
1087
+ mock_credentials .access_key = "ak"
1088
+ mock_credentials .secret_key = "sk"
1089
+ mock_credentials .token = None
1090
+
1091
+ with patch .object (
1092
+ guardrail .async_handler , "post" , new_callable = AsyncMock
1093
+ ) as mock_post , patch .object (
1094
+ guardrail , "_load_credentials" , return_value = (mock_credentials , "us-east-1" )
1095
+ ), patch .object (
1096
+ guardrail ,
1097
+ "_prepare_request" ,
1098
+ return_value = MagicMock (url = "http://example" , headers = {}, body = b"" ),
1099
+ ), patch .object (
1100
+ guardrail , "add_standard_logging_guardrail_information_to_request_data"
1101
+ ) as mock_add_trace :
1102
+ mock_post .return_value = mock_resp
1103
+
1104
+ with pytest .raises (HTTPException ) as excinfo :
1105
+ await guardrail .make_bedrock_api_request (
1106
+ source = "INPUT" ,
1107
+ messages = request_data ["messages" ],
1108
+ request_data = request_data ,
1109
+ )
1110
+
1111
+ # 1) Raised HTTPException with 400 status
1112
+ err = excinfo .value
1113
+ assert err .status_code == 400
1114
+ assert err .detail ["error" ] == "Guardrail application failed."
1115
+
1116
+ # 2) Detail includes the Output object from the Bedrock body
1117
+ assert err .detail ["bedrock_guardrail_response" ] == payload ["Output" ]
1118
+
1119
+ # 3) Trace logging received a 'failure' status
1120
+ assert mock_add_trace .called
1121
+ _ , kwargs = mock_add_trace .call_args
1122
+ assert kwargs ["guardrail_status" ] == "failure"
1123
+ # And the JSON passed to tracing is the same response we received
1124
+ assert kwargs ["guardrail_json_response" ] == payload
0 commit comments