Files
wehub-resource-sync b957a53def
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:21:23 +08:00

752 lines
28 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
from unittest.mock import AsyncMock, Mock, PropertyMock, patch
import boto3
import pytest
from semantic_kernel.agents.bedrock.action_group_utils import (
kernel_function_to_bedrock_function_schema,
parse_function_result_contents,
)
from semantic_kernel.agents.bedrock.bedrock_agent import BedrockAgent, BedrockAgentThread
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.contents.function_result_content import FunctionResultContent
from semantic_kernel.exceptions.agent_exceptions import AgentInitializationException, AgentInvokeException
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
# region Agent Initialization Tests
# Test case to verify BedrockAgent initialization
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_initialization(client, bedrock_agent_model_with_id):
agent = BedrockAgent(bedrock_agent_model_with_id)
assert agent.name == bedrock_agent_model_with_id.agent_name
assert agent.agent_model.agent_name == bedrock_agent_model_with_id.agent_name
assert agent.agent_model.agent_id == bedrock_agent_model_with_id.agent_id
assert agent.agent_model.foundation_model == bedrock_agent_model_with_id.foundation_model
# Test case to verify error handling during BedrockAgent initialization with non-auto function choice
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_initialization_error_with_non_auto_function_choice(client, bedrock_agent_model_with_id):
with pytest.raises(ValueError, match="Only FunctionChoiceType.AUTO is supported."):
BedrockAgent(
bedrock_agent_model_with_id,
function_choice_behavior=FunctionChoiceBehavior.NoneInvoke(),
)
# Test case to verify the creation of BedrockAgent
@patch.object(boto3, "client", return_value=Mock())
@pytest.mark.parametrize(
"kernel, function_choice_behavior, arguments",
[
(None, None, None),
(Kernel(), None, None),
(Kernel(), FunctionChoiceBehavior.Auto(), None),
(Kernel(), FunctionChoiceBehavior.Auto(), KernelArguments()),
],
)
async def test_bedrock_agent_create_and_prepare_agent(
client,
bedrock_agent_model_with_id_not_prepared_dict,
bedrock_agent_unit_test_env,
kernel,
function_choice_behavior,
arguments,
):
with (
patch.object(client, "create_agent") as mock_create_agent,
patch.object(BedrockAgent, "_wait_for_agent_status", new_callable=AsyncMock),
patch.object(BedrockAgent, "prepare_agent_and_wait_until_prepared", new_callable=AsyncMock),
):
mock_create_agent.return_value = bedrock_agent_model_with_id_not_prepared_dict
agent = await BedrockAgent.create_and_prepare_agent(
name=bedrock_agent_model_with_id_not_prepared_dict["agent"]["agentName"],
instructions="test_instructions",
bedrock_client=client,
env_file_path="fake_path",
kernel=kernel,
function_choice_behavior=function_choice_behavior,
arguments=arguments,
)
mock_create_agent.assert_called_once_with(
agentName=bedrock_agent_model_with_id_not_prepared_dict["agent"]["agentName"],
foundationModel=bedrock_agent_unit_test_env["BEDROCK_AGENT_FOUNDATION_MODEL"],
agentResourceRoleArn=bedrock_agent_unit_test_env["BEDROCK_AGENT_AGENT_RESOURCE_ROLE_ARN"],
instruction="test_instructions",
)
assert agent.agent_model.agent_id == bedrock_agent_model_with_id_not_prepared_dict["agent"]["agentId"]
assert agent.id == bedrock_agent_model_with_id_not_prepared_dict["agent"]["agentId"]
assert agent.agent_model.agent_name == bedrock_agent_model_with_id_not_prepared_dict["agent"]["agentName"]
assert agent.name == bedrock_agent_model_with_id_not_prepared_dict["agent"]["agentName"]
assert (
agent.agent_model.foundation_model
== bedrock_agent_model_with_id_not_prepared_dict["agent"]["foundationModel"]
)
assert agent.kernel is not None
assert agent.function_choice_behavior is not None
if arguments:
assert agent.arguments is not None
# Test case to verify the creation of BedrockAgent
@pytest.mark.parametrize(
"exclude_list",
[
["BEDROCK_AGENT_AGENT_RESOURCE_ROLE_ARN"],
["BEDROCK_AGENT_FOUNDATION_MODEL"],
],
indirect=True,
)
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_create_and_prepare_agent_settings_validation_error(
client,
bedrock_agent_model_with_id_not_prepared_dict,
bedrock_agent_unit_test_env,
):
with pytest.raises(AgentInitializationException):
await BedrockAgent.create_and_prepare_agent(
name=bedrock_agent_model_with_id_not_prepared_dict["agent"]["agentName"],
instructions="test_instructions",
env_file_path="fake_path",
)
# Test case to verify the creation of BedrockAgent
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_create_and_prepare_agent_service_exception(
client,
bedrock_agent_model_with_id_not_prepared_dict,
bedrock_agent_unit_test_env,
):
with (
patch.object(client, "create_agent") as mock_create_agent,
patch.object(BedrockAgent, "prepare_agent_and_wait_until_prepared", new_callable=AsyncMock),
):
from botocore.exceptions import ClientError
mock_create_agent.side_effect = ClientError({}, "create_agent")
with pytest.raises(AgentInitializationException):
await BedrockAgent.create_and_prepare_agent(
name=bedrock_agent_model_with_id_not_prepared_dict["agent"]["agentName"],
instructions="test_instructions",
bedrock_client=client,
env_file_path="fake_path",
)
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_prepare_agent_and_wait_until_prepared(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_agent_model_with_id_preparing_dict,
bedrock_agent_model_with_id_prepared_dict,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
with (
patch.object(client, "get_agent") as mock_get_agent,
patch.object(client, "prepare_agent") as mock_prepare_agent,
):
mock_get_agent.side_effect = [
bedrock_agent_model_with_id_preparing_dict,
bedrock_agent_model_with_id_prepared_dict,
]
await agent.prepare_agent_and_wait_until_prepared()
mock_prepare_agent.assert_called_once_with(agentId=bedrock_agent_model_with_id.agent_id)
assert mock_get_agent.call_count == 2
assert agent.agent_model.agent_status == "PREPARED"
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_prepare_agent_and_wait_until_prepared_fail(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_agent_model_with_id_preparing_dict,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
with (
patch.object(client, "get_agent") as mock_get_agent,
patch.object(client, "prepare_agent"),
):
mock_get_agent.side_effect = [
bedrock_agent_model_with_id_preparing_dict,
bedrock_agent_model_with_id_preparing_dict,
bedrock_agent_model_with_id_preparing_dict,
bedrock_agent_model_with_id_preparing_dict,
bedrock_agent_model_with_id_preparing_dict,
bedrock_agent_model_with_id_preparing_dict,
]
with pytest.raises(TimeoutError):
await agent.prepare_agent_and_wait_until_prepared()
# Test case to verify the creation of a code interpreter action group
@patch.object(boto3, "client", return_value=Mock())
async def test_create_code_interpreter_action_group(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_action_group_mode_dict,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
with (
patch.object(client, "create_agent_action_group") as mock_create_action_group,
patch.object(
BedrockAgent, "prepare_agent_and_wait_until_prepared"
) as mock_prepare_agent_and_wait_until_prepared,
):
mock_create_action_group.return_value = bedrock_action_group_mode_dict
action_group_model = await agent.create_code_interpreter_action_group()
mock_create_action_group.assert_called_once_with(
agentId=agent.agent_model.agent_id,
agentVersion=agent.agent_model.agent_version or "DRAFT",
actionGroupName=f"{agent.agent_model.agent_name}_code_interpreter",
actionGroupState="ENABLED",
parentActionGroupSignature="AMAZON.CodeInterpreter",
)
assert action_group_model.action_group_id == bedrock_action_group_mode_dict["agentActionGroup"]["actionGroupId"]
mock_prepare_agent_and_wait_until_prepared.assert_called_once()
# Test case to verify the creation of BedrockAgent with plugins
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_create_with_plugin_via_constructor(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
custom_plugin_class,
):
agent = BedrockAgent(
bedrock_agent_model_with_id,
plugins=[custom_plugin_class()],
bedrock_client=client,
)
assert agent.kernel.plugins is not None
assert len(agent.kernel.plugins) == 1
# Test case to verify the creation of a user input action group
@patch.object(boto3, "client", return_value=Mock())
async def test_create_user_input_action_group(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_action_group_mode_dict,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
with (
patch.object(agent.bedrock_client, "create_agent_action_group") as mock_create_action_group,
patch.object(
BedrockAgent, "prepare_agent_and_wait_until_prepared"
) as mock_prepare_agent_and_wait_until_prepared,
):
mock_create_action_group.return_value = bedrock_action_group_mode_dict
action_group_model = await agent.create_user_input_action_group()
mock_create_action_group.assert_called_once_with(
agentId=agent.agent_model.agent_id,
agentVersion=agent.agent_model.agent_version or "DRAFT",
actionGroupName=f"{agent.agent_model.agent_name}_user_input",
actionGroupState="ENABLED",
parentActionGroupSignature="AMAZON.UserInput",
)
assert action_group_model.action_group_id == bedrock_action_group_mode_dict["agentActionGroup"]["actionGroupId"]
mock_prepare_agent_and_wait_until_prepared.assert_called_once()
# Test case to verify the creation of a kernel function action group
@patch.object(boto3, "client", return_value=Mock())
async def test_create_kernel_function_action_group(
client,
kernel_with_function,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_action_group_mode_dict,
):
agent = BedrockAgent(bedrock_agent_model_with_id, kernel=kernel_with_function, bedrock_client=client)
with (
patch.object(agent.bedrock_client, "create_agent_action_group") as mock_create_action_group,
patch.object(
BedrockAgent, "prepare_agent_and_wait_until_prepared"
) as mock_prepare_agent_and_wait_until_prepared,
):
mock_create_action_group.return_value = bedrock_action_group_mode_dict
action_group_model = await agent.create_kernel_function_action_group()
mock_create_action_group.assert_called_once_with(
agentId=agent.agent_model.agent_id,
agentVersion=agent.agent_model.agent_version or "DRAFT",
actionGroupName=f"{agent.agent_model.agent_name}_kernel_function",
actionGroupState="ENABLED",
actionGroupExecutor={"customControl": "RETURN_CONTROL"},
functionSchema=kernel_function_to_bedrock_function_schema(
agent.function_choice_behavior.get_config(kernel_with_function)
),
)
assert action_group_model.action_group_id == bedrock_action_group_mode_dict["agentActionGroup"]["actionGroupId"]
mock_prepare_agent_and_wait_until_prepared.assert_called_once()
# Test case to verify the association of an agent with a knowledge base
@patch.object(boto3, "client", return_value=Mock())
async def test_associate_agent_knowledge_base(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
with (
patch.object(agent.bedrock_client, "associate_agent_knowledge_base") as mock_associate_knowledge_base,
patch.object(
BedrockAgent, "prepare_agent_and_wait_until_prepared"
) as mock_prepare_agent_and_wait_until_prepared,
):
await agent.associate_agent_knowledge_base("test_knowledge_base_id")
mock_associate_knowledge_base.assert_called_once_with(
agentId=agent.agent_model.agent_id,
agentVersion=agent.agent_model.agent_version,
knowledgeBaseId="test_knowledge_base_id",
)
mock_prepare_agent_and_wait_until_prepared.assert_called_once()
# Test case to verify the disassociation of an agent with a knowledge base
@patch.object(boto3, "client", return_value=Mock())
async def test_disassociate_agent_knowledge_base(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
with (
patch.object(agent.bedrock_client, "disassociate_agent_knowledge_base") as mock_disassociate_knowledge_base,
patch.object(
BedrockAgent, "prepare_agent_and_wait_until_prepared"
) as mock_prepare_agent_and_wait_until_prepared,
):
await agent.disassociate_agent_knowledge_base("test_knowledge_base_id")
mock_disassociate_knowledge_base.assert_called_once_with(
agentId=agent.agent_model.agent_id,
agentVersion=agent.agent_model.agent_version,
knowledgeBaseId="test_knowledge_base_id",
)
mock_prepare_agent_and_wait_until_prepared.assert_called_once()
# Test case to verify listing associated knowledge bases with an agent
@patch.object(boto3, "client", return_value=Mock())
async def test_list_associated_agent_knowledge_bases(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
with patch.object(agent.bedrock_client, "list_agent_knowledge_bases") as mock_list_knowledge_bases:
await agent.list_associated_agent_knowledge_bases()
mock_list_knowledge_bases.assert_called_once_with(
agentId=agent.agent_model.agent_id,
agentVersion=agent.agent_model.agent_version,
)
# endregion
# region Agent Deletion Tests
@patch.object(boto3, "client", return_value=Mock())
async def test_delete_agent(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
agent_id = bedrock_agent_model_with_id.agent_id
with patch.object(agent.bedrock_client, "delete_agent") as mock_delete_agent:
await agent.delete_agent()
mock_delete_agent.assert_called_once_with(agentId=agent_id)
assert agent.agent_model.agent_id is None
# Test case to verify error handling when deleting an agent that does not exist
@patch.object(boto3, "client", return_value=Mock())
async def test_delete_agent_twice_error(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
with patch.object(agent.bedrock_client, "delete_agent"):
await agent.delete_agent()
with pytest.raises(ValueError):
await agent.delete_agent()
# Test case to verify error handling when there is a client error during agent deletion
@patch.object(boto3, "client", return_value=Mock())
async def test_delete_agent_client_error(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
):
agent = BedrockAgent(bedrock_agent_model_with_id, bedrock_client=client)
with patch.object(agent.bedrock_client, "delete_agent") as mock_delete_agent:
from botocore.exceptions import ClientError
mock_delete_agent.side_effect = ClientError({"Error": {"Code": "500"}}, "delete_agent")
with pytest.raises(ClientError):
await agent.delete_agent()
# endregion
# region Agent Invoke Tests
# Test case to verify the `get_response` method of BedrockAgent
@pytest.mark.parametrize(
"thread",
[
None,
BedrockAgentThread(None, session_id="test_session_id"),
],
)
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_get_response(
client,
thread,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_agent_non_streaming_simple_response,
simple_response,
):
with (
patch.object(BedrockAgent, "_invoke_agent", new_callable=AsyncMock) as mock_invoke_agent,
patch.object(BedrockAgentThread, "create", new_callable=AsyncMock) as mock_start,
patch(
"semantic_kernel.agents.bedrock.bedrock_agent.BedrockAgentThread.id",
new_callable=PropertyMock,
) as mock_id,
):
agent = BedrockAgent(bedrock_agent_model_with_id)
mock_id.return_value = "mock-thread-id"
mock_invoke_agent.return_value = bedrock_agent_non_streaming_simple_response
mock_start.return_value = "test_session_id"
response = await agent.get_response(messages="test_input_text", thread=thread)
assert response.message.content == simple_response
mock_invoke_agent.assert_called_once()
# Test case to verify the `get_response` method of BedrockAgent
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_get_response_exception(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_agent_non_streaming_empty_response,
):
with (
patch.object(BedrockAgent, "_invoke_agent", new_callable=AsyncMock) as mock_invoke_agent,
patch.object(BedrockAgentThread, "create", new_callable=AsyncMock) as mock_start,
patch(
"semantic_kernel.agents.bedrock.bedrock_agent.BedrockAgentThread.id",
new_callable=PropertyMock,
) as mock_id,
):
agent = BedrockAgent(bedrock_agent_model_with_id)
mock_id.return_value = "mock-thread-id"
mock_invoke_agent.return_value = bedrock_agent_non_streaming_empty_response
mock_start.return_value = "test_session_id"
with pytest.raises(AgentInvokeException):
await agent.get_response(messages="test_input_text")
# Test case to verify the invocation of BedrockAgent
@pytest.mark.parametrize(
"thread",
[
None,
BedrockAgentThread(None, session_id="test_session_id"),
],
)
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_invoke(
client,
thread,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_agent_non_streaming_simple_response,
simple_response,
):
with (
patch.object(BedrockAgent, "_invoke_agent", new_callable=AsyncMock) as mock_invoke_agent,
patch.object(BedrockAgentThread, "create", new_callable=AsyncMock) as mock_start,
patch.object(BedrockAgentThread, "id", "test_session_id"),
):
agent = BedrockAgent(bedrock_agent_model_with_id)
mock_invoke_agent.return_value = bedrock_agent_non_streaming_simple_response
mock_start.return_value = "test_session_id"
async for response in agent.invoke(messages="test_input_text", thread=thread):
assert response.message.content == simple_response
mock_invoke_agent.assert_called_once_with(
"test_session_id",
"test_input_text",
None,
streamingConfigurations={"streamFinalResponse": False},
sessionState={},
)
# Test case to verify the streaming invocation of BedrockAgent
@pytest.mark.parametrize(
"thread",
[
None,
BedrockAgentThread(None, session_id="test_session_id"),
],
)
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_invoke_stream(
client,
thread,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_agent_streaming_simple_response,
simple_response,
):
with (
patch.object(BedrockAgent, "_invoke_agent", new_callable=AsyncMock) as mock_invoke_agent,
patch.object(BedrockAgentThread, "create", new_callable=AsyncMock) as mock_start,
patch.object(BedrockAgentThread, "id", "test_session_id"),
):
agent = BedrockAgent(bedrock_agent_model_with_id)
mock_invoke_agent.return_value = bedrock_agent_streaming_simple_response
mock_start.return_value = "test_session_id"
full_message = ""
async for response in agent.invoke_stream(messages="test_input_text", thread=thread):
full_message += response.message.content
assert full_message == simple_response
mock_invoke_agent.assert_called_once_with(
"test_session_id",
"test_input_text",
None,
streamingConfigurations={"streamFinalResponse": True},
sessionState={},
)
# Test case to verify the invocation of BedrockAgent with function call
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_invoke_with_function_call(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_agent_function_call_response,
bedrock_agent_non_streaming_simple_response,
):
with (
patch.object(BedrockAgent, "_invoke_agent", new_callable=AsyncMock) as mock_invoke_agent,
patch.object(BedrockAgent, "_handle_function_call_contents") as mock_handle_function_call_contents,
patch.object(BedrockAgentThread, "create", new_callable=AsyncMock) as mock_start,
patch.object(BedrockAgentThread, "id", "test_session_id"),
):
agent = BedrockAgent(bedrock_agent_model_with_id)
function_result_contents = [
FunctionResultContent(
id="test_id",
name="test_function",
result="test_result",
metadata={"functionInvocationInput": {"actionGroup": "test_action_group"}},
)
]
mock_handle_function_call_contents.return_value = function_result_contents
agent.function_choice_behavior.maximum_auto_invoke_attempts = 2
mock_invoke_agent.side_effect = [
bedrock_agent_function_call_response,
bedrock_agent_non_streaming_simple_response,
]
mock_start.return_value = "test_session_id"
async for _ in agent.invoke(messages="test_input_text"):
mock_invoke_agent.assert_called_with(
"test_session_id",
"test_input_text",
None,
streamingConfigurations={"streamFinalResponse": False},
sessionState={
"invocationId": "test_invocation_id",
"returnControlInvocationResults": parse_function_result_contents(function_result_contents),
},
)
# Test case to verify the streaming invocation of BedrockAgent with function call
@patch.object(boto3, "client", return_value=Mock())
async def test_bedrock_agent_invoke_stream_with_function_call(
client,
bedrock_agent_unit_test_env,
bedrock_agent_model_with_id,
bedrock_agent_function_call_response,
bedrock_agent_streaming_simple_response,
):
with (
patch.object(BedrockAgent, "_invoke_agent", new_callable=AsyncMock) as mock_invoke_agent,
patch.object(BedrockAgent, "_handle_function_call_contents") as mock_handle_function_call_contents,
patch.object(BedrockAgentThread, "create", new_callable=AsyncMock) as mock_start,
patch.object(BedrockAgentThread, "id", "test_session_id"),
):
agent = BedrockAgent(bedrock_agent_model_with_id)
function_result_contents = [
FunctionResultContent(
id="test_id",
name="test_function",
result="test_result",
metadata={"functionInvocationInput": {"actionGroup": "test_action_group"}},
)
]
mock_handle_function_call_contents.return_value = function_result_contents
agent.function_choice_behavior.maximum_auto_invoke_attempts = 2
mock_invoke_agent.side_effect = [
bedrock_agent_function_call_response,
bedrock_agent_streaming_simple_response,
]
mock_start.return_value = "test_session_id"
async for _ in agent.invoke_stream(messages="test_input_text"):
mock_invoke_agent.assert_called_with(
"test_session_id",
"test_input_text",
None,
streamingConfigurations={"streamFinalResponse": True},
sessionState={
"invocationId": "test_invocation_id",
"returnControlInvocationResults": parse_function_result_contents(function_result_contents),
},
)
# endregion
# region Filename Sanitization Tests
def test_sanitize_filename_simple():
"""Test _sanitize_filename with a simple filename."""
assert BedrockAgent._sanitize_filename("file.txt") == "file.txt"
def test_sanitize_filename_with_spaces():
"""Test _sanitize_filename with spaces in filename."""
assert BedrockAgent._sanitize_filename("my file.txt") == "my file.txt"
def test_sanitize_filename_directory_traversal_unix():
"""Test _sanitize_filename strips Unix-style directory traversal."""
assert BedrockAgent._sanitize_filename("../../../etc/passwd") == "passwd"
assert BedrockAgent._sanitize_filename("../../file.txt") == "file.txt"
assert BedrockAgent._sanitize_filename("/etc/passwd") == "passwd"
def test_sanitize_filename_directory_traversal_windows():
"""Test _sanitize_filename strips Windows-style directory traversal."""
assert BedrockAgent._sanitize_filename("..\\..\\..\\Windows\\System32\\config") == "config"
assert BedrockAgent._sanitize_filename("C:\\Users\\file.txt") == "file.txt"
assert BedrockAgent._sanitize_filename("\\\\server\\share\\file.txt") == "file.txt"
def test_sanitize_filename_mixed_separators():
"""Test _sanitize_filename with mixed path separators."""
assert BedrockAgent._sanitize_filename("../path\\to/file.txt") == "file.txt"
assert BedrockAgent._sanitize_filename("..\\path/to\\file.txt") == "file.txt"
def test_sanitize_filename_null_byte():
"""Test _sanitize_filename removes null bytes."""
assert BedrockAgent._sanitize_filename("file\x00.txt") == "file.txt"
assert BedrockAgent._sanitize_filename("file.txt\x00.exe") == "file.txt.exe"
def test_sanitize_filename_empty():
"""Test _sanitize_filename returns empty string for empty result."""
assert BedrockAgent._sanitize_filename("") == ""
assert BedrockAgent._sanitize_filename("../") == ""
assert BedrockAgent._sanitize_filename("..\\") == ""
def test_sanitize_filename_only_dots():
"""Test _sanitize_filename handles edge cases with dots."""
# Note: os.path.basename("..") returns ".." which is kept as-is
# Only "../" or "..\" patterns get stripped to empty string
assert BedrockAgent._sanitize_filename(".") == "."
def test_sanitize_filename_logs_warning(caplog):
"""Test _sanitize_filename logs warning when filename is sanitized."""
import logging
with caplog.at_level(logging.WARNING):
result = BedrockAgent._sanitize_filename("../malicious/file.txt")
assert result == "file.txt"
assert "potentially malicious path components" in caplog.text
assert "../malicious/file.txt" in caplog.text
assert "file.txt" in caplog.text
def test_sanitize_filename_no_warning_for_clean_filename(caplog):
"""Test _sanitize_filename does not log warning for clean filenames."""
import logging
with caplog.at_level(logging.WARNING):
result = BedrockAgent._sanitize_filename("clean_file.txt")
assert result == "clean_file.txt"
assert "potentially malicious" not in caplog.text
# endregion