Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

329 lines
12 KiB
Python

"""Direct unit tests for integrations/aws/aws_sdk_client.py."""
from __future__ import annotations
from datetime import UTC, datetime
from unittest.mock import MagicMock, patch
import pytest
from botocore.exceptions import ClientError, NoCredentialsError, ParamValidationError
from integrations.aws.aws_sdk_client import (
MAX_LIST_ITEMS,
_is_operation_allowed,
_sanitize_response,
execute_aws_sdk_call,
)
class TestIsOperationAllowedAllowlist:
@pytest.mark.parametrize(
"op",
[
"describe_instances",
"describe_db_instances",
"get_role",
"get_function_configuration",
"list_functions",
"list_clusters",
"head_object",
"head_bucket",
"query",
"scan",
"select_object_content",
"batch_get_item",
"lookup_events",
],
)
def test_allowed_operations(self, op: str) -> None:
allowed, reason = _is_operation_allowed(op)
assert allowed is True, f"{op} should be allowed: {reason}"
assert reason == "Operation allowed"
class TestIsOperationAllowedBlocklist:
@pytest.mark.parametrize(
"op",
[
"delete_bucket",
"remove_tags",
"update_function_configuration",
"put_object",
"create_stack",
"modify_db_instance",
"terminate_instances",
"stop_instances",
"start_instances",
"reboot_instances",
"attach_volume",
"detach_volume",
"associate_route_table",
"disassociate_route_table",
],
)
def test_blocked_operations(self, op: str) -> None:
allowed, reason = _is_operation_allowed(op)
assert allowed is False, f"{op} should be blocked"
assert "blocked pattern" in reason
def test_blocklist_takes_precedence_over_allowlist(self) -> None:
allowed, _ = _is_operation_allowed("get_and_delete_item")
assert allowed is False
def test_unknown_operation_rejected(self) -> None:
allowed, reason = _is_operation_allowed("invoke_function")
assert allowed is False
assert "does not match any allowed patterns" in reason
def test_case_insensitivity(self) -> None:
allowed, _ = _is_operation_allowed("Describe_Instances")
assert allowed is True
allowed, _ = _is_operation_allowed("Delete_Bucket")
assert allowed is False
class TestSanitizeResponseDatetime:
def test_datetime_converted_to_iso(self) -> None:
dt = datetime(2024, 6, 15, 12, 30, 0, tzinfo=UTC)
assert _sanitize_response(dt) == "2024-06-15T12:30:00+00:00"
def test_datetime_nested_in_dict(self) -> None:
dt = datetime(2024, 1, 1, tzinfo=UTC)
result = _sanitize_response({"LaunchTime": dt})
assert result["LaunchTime"] == "2024-01-01T00:00:00+00:00"
class TestSanitizeResponseBytes:
def test_bytes_replaced_with_placeholder(self) -> None:
result = _sanitize_response(b"\x00\x01\x02")
assert result == "<binary data: 3 bytes>"
def test_bytes_nested_in_dict(self) -> None:
result = _sanitize_response({"Body": b"hello"})
assert result["Body"] == "<binary data: 5 bytes>"
class TestSanitizeResponseDeepNesting:
def test_max_depth_reached(self) -> None:
default_max_depth = 10
data: dict = {"leaf": "value"}
for _ in range(default_max_depth + 2):
data = {"nested": data}
result = _sanitize_response(data)
current = result
for _ in range(default_max_depth + 1):
current = current["nested"]
assert current == "... (max depth reached)"
def test_custom_max_depth(self) -> None:
data: dict = {"leaf": "value"}
for _ in range(5):
data = {"nested": data}
result = _sanitize_response(data, max_depth=3)
current = result
for _ in range(4):
current = current["nested"]
assert current == "... (max depth reached)"
class TestSanitizeResponseOversizedLists:
def test_list_over_max_is_truncated(self) -> None:
big_list = list(range(MAX_LIST_ITEMS + 50))
result = _sanitize_response(big_list)
assert len(result) == MAX_LIST_ITEMS + 1
assert "50 more items truncated" in result[-1]
def test_list_exactly_at_max_not_truncated(self) -> None:
exact_list = list(range(MAX_LIST_ITEMS))
result = _sanitize_response(exact_list)
assert len(result) == MAX_LIST_ITEMS
def test_list_under_max_not_truncated(self) -> None:
small_list = [1, 2, 3]
result = _sanitize_response(small_list)
assert result == [1, 2, 3]
class TestSanitizeResponseMisc:
def test_none_returns_none(self) -> None:
assert _sanitize_response(None) is None
def test_primitive_passthrough(self) -> None:
assert _sanitize_response(42) == 42
assert _sanitize_response("hello") == "hello"
assert _sanitize_response(3.14) == 3.14
def test_response_metadata_stripped(self) -> None:
data = {
"Instances": [{"InstanceId": "i-1"}],
"ResponseMetadata": {"RequestId": "abc", "HTTPStatusCode": 200},
}
result = _sanitize_response(data)
assert "ResponseMetadata" not in result
assert result["Instances"] == [{"InstanceId": "i-1"}]
def test_tuple_handled(self) -> None:
result = _sanitize_response((1, 2, 3))
assert result == [1, 2, 3]
def test_complex_nested_structure(self) -> None:
dt = datetime(2024, 6, 1, tzinfo=UTC)
data = {
"Reservations": [
{
"Instances": [
{
"InstanceId": "i-abc",
"LaunchTime": dt,
"Tags": [{"Key": "Name", "Value": "web"}],
}
]
}
],
"ResponseMetadata": {"HTTPStatusCode": 200},
}
result = _sanitize_response(data)
assert "ResponseMetadata" not in result
inst = result["Reservations"][0]["Instances"][0]
assert inst["InstanceId"] == "i-abc"
assert inst["LaunchTime"] == "2024-06-01T00:00:00+00:00"
@pytest.fixture()
def mock_boto3_client():
fake_client = MagicMock()
fake_client.meta.region_name = "us-east-1"
with patch("integrations.aws.aws_sdk_client.boto3.client", return_value=fake_client):
yield fake_client
class TestExecuteAwsSdkCallHappyPath:
def test_success_with_no_params(self, mock_boto3_client) -> None:
mock_boto3_client.describe_instances.return_value = {
"Reservations": [],
"ResponseMetadata": {"HTTPStatusCode": 200},
}
result = execute_aws_sdk_call("ec2", "describe_instances")
assert result["success"] is True
assert result["service"] == "ec2"
assert result["operation"] == "describe_instances"
assert "ResponseMetadata" not in result["data"]
assert result["metadata"]["region"] == "us-east-1"
def test_success_with_params(self, mock_boto3_client) -> None:
mock_boto3_client.describe_instances.return_value = {
"Reservations": [{"Instances": [{"InstanceId": "i-1234"}]}],
"ResponseMetadata": {"HTTPStatusCode": 200},
}
params = {"Filters": [{"Name": "instance-state-name", "Values": ["running"]}]}
result = execute_aws_sdk_call("ec2", "describe_instances", parameters=params)
assert result["success"] is True
mock_boto3_client.describe_instances.assert_called_once_with(**params)
def test_region_override(self, mock_boto3_client) -> None:
mock_boto3_client.list_clusters.return_value = {"ClusterArns": []}
with patch(
"integrations.aws.aws_sdk_client.boto3.client", return_value=mock_boto3_client
) as m:
execute_aws_sdk_call("ecs", "list_clusters", region="eu-west-1")
m.assert_called_once_with("ecs", region_name="eu-west-1")
class TestExecuteAwsSdkCallValidation:
def test_empty_service_name(self) -> None:
result = execute_aws_sdk_call("", "describe_instances")
assert result["success"] is False
assert "required" in result["error"]
def test_empty_operation_name(self) -> None:
result = execute_aws_sdk_call("ec2", "")
assert result["success"] is False
assert "required" in result["error"]
def test_blocked_operation(self) -> None:
result = execute_aws_sdk_call("ec2", "terminate_instances")
assert result["success"] is False
assert "not allowed" in result["error"].lower()
assert result["metadata"]["validation_failed"] is True
def test_unknown_operation_pattern(self) -> None:
result = execute_aws_sdk_call("ec2", "invoke_magic")
assert result["success"] is False
assert "not allowed" in result["error"].lower()
def test_rejected_operation_does_not_create_client(self) -> None:
with patch("integrations.aws.aws_sdk_client.boto3.client") as client:
result = execute_aws_sdk_call("ec2", "terminate_instances")
assert result["success"] is False
client.assert_not_called()
class TestExecuteAwsSdkCallMissingOperation:
def test_operation_not_found_on_service(self, mock_boto3_client) -> None:
# MagicMock creates arbitrary attributes unless they are explicitly deleted.
del mock_boto3_client.describe_nonexistent
result = execute_aws_sdk_call("ec2", "describe_nonexistent")
assert result["success"] is False
assert "not found" in result["error"].lower()
class TestExecuteAwsSdkCallCredentialErrors:
def test_no_credentials_error_when_creating_client(self) -> None:
with patch(
"integrations.aws.aws_sdk_client.boto3.client",
side_effect=NoCredentialsError(),
):
result = execute_aws_sdk_call("ec2", "describe_instances")
assert result["success"] is False
assert "credentials" in result["error"].lower()
assert result["metadata"]["error_type"] == "credentials"
def test_no_credentials_error(self, mock_boto3_client) -> None:
mock_boto3_client.describe_instances.side_effect = NoCredentialsError()
result = execute_aws_sdk_call("ec2", "describe_instances")
assert result["success"] is False
assert "credentials" in result["error"].lower()
assert result["metadata"]["error_type"] == "credentials"
class TestExecuteAwsSdkCallParamValidation:
def test_param_validation_error(self, mock_boto3_client) -> None:
mock_boto3_client.describe_instances.side_effect = ParamValidationError(
report="Missing required param: InstanceIds"
)
result = execute_aws_sdk_call("ec2", "describe_instances", parameters={"bad": "param"})
assert result["success"] is False
assert "invalid parameters" in result["error"].lower()
assert result["metadata"]["error_type"] == "validation"
class TestExecuteAwsSdkCallClientError:
def test_client_error(self, mock_boto3_client) -> None:
mock_boto3_client.describe_instances.side_effect = ClientError(
{
"Error": {"Code": "UnauthorizedAccess", "Message": "Not authorized"},
"ResponseMetadata": {"HTTPStatusCode": 403},
},
"DescribeInstances",
)
result = execute_aws_sdk_call("ec2", "describe_instances")
assert result["success"] is False
assert "UnauthorizedAccess" in result["error"]
assert result["metadata"]["error_type"] == "client_error"
assert result["metadata"]["error_code"] == "UnauthorizedAccess"
assert result["metadata"]["status_code"] == 403
class TestExecuteAwsSdkCallUnexpectedError:
def test_unexpected_runtime_error(self, mock_boto3_client) -> None:
mock_boto3_client.describe_instances.side_effect = RuntimeError("boom")
result = execute_aws_sdk_call("ec2", "describe_instances")
assert result["success"] is False
assert "unexpected error" in result["error"].lower()
assert result["metadata"]["error_type"] == "unexpected"