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

297 lines
10 KiB
Python

"""Unit tests for the Lambda service client."""
import base64
import json
from io import BytesIO
from unittest.mock import MagicMock, patch
from zipfile import ZipFile
import pytest
from botocore.exceptions import ClientError
from integrations.aws.lambda_client import (
get_function_code,
get_function_configuration,
get_invocation_logs_by_request_id,
get_recent_invocations,
invoke_function,
list_functions,
)
@pytest.fixture(autouse=True)
def mock_aws_credentials():
with patch("integrations.aws.lambda_client.require_aws_credentials", return_value=None):
yield
@pytest.fixture
def mock_lambda_client():
with patch("integrations.aws.lambda_client._get_lambda_client") as mock:
client = MagicMock()
mock.return_value = client
yield client
@pytest.fixture
def mock_logs_client():
with patch("integrations.aws.lambda_client._get_cloudwatch_logs_client") as mock:
client = MagicMock()
mock.return_value = client
yield client
def test_get_function_configuration_success(mock_lambda_client) -> None:
mock_lambda_client.get_function_configuration.return_value = {
"FunctionName": "test-func",
"FunctionArn": "arn:aws:lambda:us-east-1:123:function:test-func",
"Runtime": "python3.12",
"Handler": "index.handler",
"CodeSize": 1024,
"Timeout": 30,
"MemorySize": 128,
"LastModified": "2024-01-01T00:00:00.000+0000",
"Role": "arn:aws:iam::123:role/lambda-role",
"Environment": {"Variables": {"KEY": "VALUE"}},
"Description": "Test function",
"Version": "$LATEST",
"State": "Active",
"Layers": [{"Arn": "arn:layer", "CodeSize": 500}],
}
result = get_function_configuration("test-func")
assert result["success"] is True
data = result["data"]
assert data["function_name"] == "test-func"
assert data["runtime"] == "python3.12"
assert data["environment"] == {"KEY": "VALUE"}
assert data["layers"][0]["arn"] == "arn:layer"
def test_get_function_configuration_error(mock_lambda_client) -> None:
error_response = {"Error": {"Code": "ResourceNotFoundException", "Message": "Not found"}}
mock_lambda_client.get_function_configuration.side_effect = ClientError(
error_response, "GetFunctionConfiguration"
)
result = get_function_configuration("missing-func")
assert result["success"] is False
assert "ResourceNotFoundException" in result["error"]
def test_get_function_code_success(mock_lambda_client) -> None:
mock_lambda_client.get_function.return_value = {
"Code": {"Location": "https://example.com/zip", "RepositoryType": "S3"},
"Configuration": {"CodeSize": 2048},
}
# Create a mock zip file
zip_buffer = BytesIO()
with ZipFile(zip_buffer, "w") as zf:
zf.writestr("index.py", "def handler(): pass")
zf.writestr("data.bin", b"\x80\x81\x82") # Invalid UTF-8
zf.writestr("large.txt", "a" * 100) # Will be "large" relative to max_file_size
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.content = zip_buffer.getvalue()
with patch("requests.get", return_value=mock_response):
result = get_function_code("test-func", max_file_size=50)
assert result["success"] is True
files = result["data"]["files"]
assert "index.py" in files
assert files["index.py"]["content"] == "def handler(): pass"
assert files["data.bin"]["binary"] is True
assert files["large.txt"]["truncated"] is True
assert result["data"]["file_count"] == 3
def test_get_recent_invocations_success(mock_logs_client) -> None:
mock_logs_client.filter_log_events.return_value = {
"events": [
{"timestamp": 1000, "message": "START RequestId: req1 Version: $LATEST\n"},
{"timestamp": 1100, "message": "hello world\n"},
{"timestamp": 1200, "message": "END RequestId: req1\n"},
{
"timestamp": 1300,
"message": "REPORT RequestId: req1\tDuration: 200.00 ms\tBilled Duration: 200 ms\tMemory Size: 128 MB\tMax Memory Used: 64 MB\t\n",
},
]
}
result = get_recent_invocations("test-func")
assert result["success"] is True
assert result["data"]["invocation_count"] == 1
invocation = result["data"]["invocations"][0]
assert invocation["request_id"] == "req1"
assert invocation["duration_ms"] == 200.0
assert invocation["memory_used_mb"] == 64
assert len(invocation["logs"]) == 4
def test_get_recent_invocations_resource_not_found(mock_logs_client) -> None:
error_response = {
"Error": {"Code": "ResourceNotFoundException", "Message": "Log group not found"}
}
mock_logs_client.filter_log_events.side_effect = ClientError(error_response, "FilterLogEvents")
result = get_recent_invocations("missing-func")
assert result["success"] is False
assert "Log group not found" in result["error"]
def test_invoke_function_success(mock_lambda_client) -> None:
mock_response = {
"StatusCode": 200,
"ExecutedVersion": "$LATEST",
"Payload": BytesIO(json.dumps({"body": "ok"}).encode()),
"LogResult": base64.b64encode(b"some logs").decode(),
}
mock_lambda_client.invoke.return_value = mock_response
result = invoke_function("test-func", payload={"key": "val"})
assert result["success"] is True
assert result["data"]["payload"] == {"body": "ok"}
assert result["data"]["log_result"] == "some logs"
# Verify call
mock_lambda_client.invoke.assert_called_once_with(
FunctionName="test-func",
InvocationType="RequestResponse",
Payload=json.dumps({"key": "val"}),
)
def test_get_function_code_no_extract(mock_lambda_client) -> None:
mock_lambda_client.get_function.return_value = {
"Code": {"Location": "https://example.com/zip", "RepositoryType": "S3"},
"Configuration": {"CodeSize": 1024},
}
# Should not call requests.get
with patch("requests.get") as mock_get:
result = get_function_code("test-func", extract_files=False)
assert result["success"] is True
assert "files" not in result["data"]
mock_get.assert_not_called()
def test_get_function_code_too_large(mock_lambda_client) -> None:
mock_lambda_client.get_function.return_value = {
"Code": {"Location": "https://example.com/zip", "RepositoryType": "S3"},
"Configuration": {"CodeSize": 10 * 1024 * 1024}, # 10MB
}
with patch("requests.get") as mock_get:
result = get_function_code("test-func")
assert result["success"] is True
assert "files" not in result["data"]
mock_get.assert_not_called()
def test_get_invocation_logs_by_request_id_success(mock_logs_client) -> None:
mock_logs_client.filter_log_events.return_value = {
"events": [
{"message": "log 1\n"},
{"message": "log 2\n"},
]
}
result = get_invocation_logs_by_request_id("test-func", "req1")
assert result["success"] is True
assert result["data"]["event_count"] == 2
assert result["data"]["logs"] == ["log 1\n", "log 2\n"]
def test_list_functions_success(mock_lambda_client) -> None:
mock_lambda_client.list_functions.return_value = {
"Functions": [
{
"FunctionName": "func1",
"FunctionArn": "arn1",
"Runtime": "python3.12",
"Handler": "h1",
"CodeSize": 100,
"Timeout": 10,
"MemorySize": 128,
"LastModified": "date1",
}
]
}
result = list_functions()
assert result["success"] is True
assert len(result["data"]["functions"]) == 1
assert result["data"]["functions"][0]["function_name"] == "func1"
def test_get_function_code_download_error(mock_lambda_client) -> None:
mock_lambda_client.get_function.return_value = {
"Code": {"Location": "https://example.com/zip", "RepositoryType": "S3"},
"Configuration": {"CodeSize": 1024},
}
mock_response = MagicMock()
mock_response.status_code = 404
with patch("requests.get", return_value=mock_response):
result = get_function_code("test-func")
assert result["success"] is True # The get_function call succeeded
assert "files" not in result["data"]
def test_get_function_code_corrupt_zip(mock_lambda_client) -> None:
mock_lambda_client.get_function.return_value = {
"Code": {"Location": "https://example.com/zip", "RepositoryType": "S3"},
"Configuration": {"CodeSize": 1024},
}
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.content = b"not a zip file"
with patch("requests.get", return_value=mock_response):
result = get_function_code("test-func")
assert result["success"] is True
assert "extract_error" in result["data"]
def test_get_recent_invocations_multiple(mock_logs_client) -> None:
# Test grouping of multiple overlapping/sequential invocations
mock_logs_client.filter_log_events.return_value = {
"events": [
{"timestamp": 1000, "message": "START RequestId: req1\n"},
{"timestamp": 1050, "message": "log 1\n"},
{"timestamp": 1100, "message": "END RequestId: req1\n"},
{
"timestamp": 1150,
"message": "REPORT RequestId: req1\tDuration: 100.00 ms\tBilled Duration: 100 ms\tMemory Size: 128 MB\tMax Memory Used: 64 MB\t\n",
},
{"timestamp": 1200, "message": "START RequestId: req2\n"},
{"timestamp": 1250, "message": "log 2\n"},
{
"timestamp": 1300,
"message": "REPORT RequestId: req2\tDuration: 50.00 ms\tBilled Duration: 50 ms\tMemory Size: 128 MB\tMax Memory Used: 32 MB\t\n",
},
]
}
result = get_recent_invocations("test-func")
assert result["success"] is True
assert result["data"]["invocation_count"] == 2
inv1 = result["data"]["invocations"][0]
inv2 = result["data"]["invocations"][1]
assert inv1["request_id"] == "req1"
assert inv1["duration_ms"] == 100.0
assert inv2["request_id"] == "req2"
assert inv2["duration_ms"] == 50.0
assert len(inv1["logs"]) == 4
assert len(inv2["logs"]) == 3