Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b8705fb
feat(runtime): Add generic handler factory for serverless execution
deanq Jan 3, 2026
8c0b62a
feat(cli): Add handler generator, manifest builder, and scanner for b…
deanq Jan 3, 2026
c14ed9f
test(runtime): Add comprehensive tests for generic handler
deanq Jan 3, 2026
8c84c34
test(cli): Add tests for handler generation, manifest building, and s…
deanq Jan 3, 2026
cc77fa5
docs(runtime): Document generic handler factory architecture
deanq Jan 3, 2026
72ff4a1
docs(cli): Add flash build command documentation
deanq Jan 3, 2026
e761d48
docs: Add build process and handler generation section to README
deanq Jan 3, 2026
9af1505
feat(cli): Integrate build utilities into flash build command
deanq Jan 3, 2026
b1968d6
refactor(build): Fix directory structure and add comprehensive error …
deanq Jan 3, 2026
8717dc3
feat(resources): Add LoadBalancerSlsResource for LB endpoints
deanq Jan 4, 2026
3cdb565
fix(test): Fix LoadBalancerSlsResource deployment test mocks
deanq Jan 4, 2026
daa1375
feat(resources): Phase 1 - Core infrastructure for @remote on LB endp…
deanq Jan 4, 2026
d02082b
feat(build): Phase 2.1 - Enhanced scanner for HTTP routing extraction
deanq Jan 4, 2026
e83c4f0
feat(build): Phase 2.2 - Updated manifest schema for HTTP routing
deanq Jan 4, 2026
3b41ca4
feat(cli): Add LB handler generator for FastAPI app creation
deanq Jan 4, 2026
6cc2888
feat(runtime): Implement LB handler factory for FastAPI app creation
deanq Jan 4, 2026
babfe12
feat(cli): Route build command to separate handlers for LB endpoints
deanq Jan 4, 2026
c9a160b
feat(resources): Add LiveLoadBalancer for local LB endpoint testing
deanq Jan 4, 2026
7f1961b
test(stubs): Add comprehensive unit tests for LoadBalancerSlsStub
deanq Jan 4, 2026
bc8f733
fix(test): Correct LB endpoint test decorator to match assertions
deanq Jan 4, 2026
79e8f88
docs: Add comprehensive documentation for @remote with LoadBalancer e…
deanq Jan 4, 2026
47d73f8
security: Remove /execute from deployed LoadBalancer endpoints
deanq Jan 4, 2026
2353c69
feat(build): Phase 4 - Fix LiveLoadBalancer handler generation to inc…
deanq Jan 4, 2026
d86b58c
fix(scanner): Discover LoadBalancer resources in addition to Serverle…
deanq Jan 4, 2026
db28ae0
chore: Format code for line length and remove unused imports
deanq Jan 4, 2026
7304d17
fix: Address PR #131 review feedback
deanq Jan 4, 2026
0218995
style: Format datetime chaining for line length
deanq Jan 4, 2026
483536b
fix: LiveLoadBalancer template not serialized to RunPod GraphQL
deanq Jan 4, 2026
ca8cd7e
fix: LoadBalancer endpoint URL and add CPU support
deanq Jan 4, 2026
17bf287
fix: Export CpuLiveLoadBalancer and CpuLoadBalancerSlsResource from t…
deanq Jan 4, 2026
a5368b7
fix: Add API key authentication to LoadBalancer health check
deanq Jan 4, 2026
8cd129a
fix(lb): Exclude flashboot from CpuLoadBalancerSlsResource GraphQL pa…
deanq Jan 4, 2026
cc73b94
fix(lb): Expand CpuInstanceType.ANY to all CPU flavors in CpuLoadBala…
deanq Jan 4, 2026
8bf1739
refactor(cpu): Move instanceIds validator to CpuEndpointMixin
deanq Jan 4, 2026
8f31e03
test: Update CPU instance test to reflect validator expansion
deanq Jan 4, 2026
5da2441
fix(lb): Increase health check timeout from 5s to 15s
deanq Jan 4, 2026
586286d
fix(lb): Fix CPU load balancer template deployment error
deanq Jan 4, 2026
1b55718
fix(http): Standardize RunPod HTTP client authentication across codebase
deanq Jan 5, 2026
8b97197
feat(http): Extend HTTP utilities to cover both sync and async authen…
deanq Jan 5, 2026
9f4e19a
fix: Address PR feedback on HTTP utilities implementation
deanq Jan 5, 2026
915f574
docs: Improve LoadBalancer documentation accuracy and completeness
deanq Jan 5, 2026
2a2a21d
test(build): Add comprehensive test coverage for scanner and handler …
deanq Jan 6, 2026
17d338a
Merge branch 'deanq/ae-1251-handler-mapper' into deanq/ae-1102-load-b…
deanq Jan 6, 2026
6d3ff3b
test(scanner): Fix resource type assertions to match scanner behavior
deanq Jan 6, 2026
707e50c
Merge branch 'main' into deanq/ae-1102-load-balancer-sls-resource
deanq Jan 8, 2026
99921d2
fix: address PR feedback - improve type hints and extract constants
deanq Jan 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
test(stubs): Add comprehensive unit tests for LoadBalancerSlsStub
Implement unit tests for LoadBalancerSlsStub covering:
- Request preparation with arguments and dependencies
- Response handling for success and error cases
- Error handling for invalid responses
- Base64 encoding/decoding of serialized data
- Endpoint URL validation
- Timeout and HTTP error handling

Test coverage:
- _prepare_request: 4 tests
- _handle_response: 5 tests
- _execute_function: 3 error case tests
- __call__: 2 integration tests

Tests verify proper function serialization, argument handling,
error propagation, and response deserialization.
  • Loading branch information
deanq committed Jan 4, 2026
commit 7f1961bdbcd6e53073b012e1a0f2cc5cfea1311c
251 changes: 251 additions & 0 deletions tests/unit/test_load_balancer_sls_stub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
"""Unit tests for LoadBalancerSlsStub functionality."""

import base64
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

import cloudpickle

from tetra_rp import remote, LoadBalancerSlsResource
from tetra_rp.stubs.load_balancer_sls import LoadBalancerSlsStub


# Create test resources
test_lb_resource = LoadBalancerSlsResource(
name="test-lb",
imageName="test:latest",
)


class TestLoadBalancerSlsStubPrepareRequest:
"""Test suite for _prepare_request method."""

def test_prepare_request_with_no_args(self):
"""Test request preparation with no arguments."""
stub = LoadBalancerSlsStub(test_lb_resource)

def test_func():
return "result"

request = stub._prepare_request(test_func, None, None, True)

assert request["function_name"] == "test_func"
assert "def test_func" in request["function_code"]
assert request["dependencies"] == []
assert request["system_dependencies"] == []
assert request["accelerate_downloads"] is True
assert "args" not in request or request["args"] == []
assert "kwargs" not in request or request["kwargs"] == {}

def test_prepare_request_with_args(self):
"""Test request preparation with positional arguments."""
stub = LoadBalancerSlsStub(test_lb_resource)

def add(x, y):
return x + y

arg1 = 5
arg2 = 3
request = stub._prepare_request(add, None, None, True, arg1, arg2)

assert request["function_name"] == "add"
assert len(request["args"]) == 2

# Verify args are properly serialized
decoded_arg1 = cloudpickle.loads(base64.b64decode(request["args"][0]))
decoded_arg2 = cloudpickle.loads(base64.b64decode(request["args"][1]))
assert decoded_arg1 == 5
assert decoded_arg2 == 3

def test_prepare_request_with_kwargs(self):
"""Test request preparation with keyword arguments."""
stub = LoadBalancerSlsStub(test_lb_resource)

def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"

request = stub._prepare_request(greet, None, None, True, name="Alice", greeting="Hi")

assert "kwargs" in request
assert len(request["kwargs"]) == 2

# Verify kwargs are properly serialized
decoded_name = cloudpickle.loads(base64.b64decode(request["kwargs"]["name"]))
decoded_greeting = cloudpickle.loads(
base64.b64decode(request["kwargs"]["greeting"])
)
assert decoded_name == "Alice"
assert decoded_greeting == "Hi"

def test_prepare_request_with_dependencies(self):
"""Test request preparation includes dependencies."""
stub = LoadBalancerSlsStub(test_lb_resource)

def test_func():
return "result"

dependencies = ["requests", "numpy"]
system_deps = ["git"]

request = stub._prepare_request(
test_func, dependencies, system_deps, True
)

assert request["dependencies"] == dependencies
assert request["system_dependencies"] == system_deps


class TestLoadBalancerSlsStubHandleResponse:
"""Test suite for _handle_response method."""

def test_handle_response_success(self):
"""Test successful response handling."""
stub = LoadBalancerSlsStub(test_lb_resource)

result_value = {"status": "ok", "value": 42}
result_b64 = base64.b64encode(cloudpickle.dumps(result_value)).decode("utf-8")

response = {"success": True, "result": result_b64}

result = stub._handle_response(response)

assert result == result_value

def test_handle_response_error(self):
"""Test error response handling."""
stub = LoadBalancerSlsStub(test_lb_resource)

response = {"success": False, "error": "Function execution failed"}

with pytest.raises(Exception, match="Remote execution failed"):
stub._handle_response(response)

def test_handle_response_invalid_type(self):
"""Test handling of invalid response type."""
stub = LoadBalancerSlsStub(test_lb_resource)

with pytest.raises(ValueError, match="Invalid response type"):
stub._handle_response("not a dict")

def test_handle_response_missing_result(self):
"""Test handling of success response without result."""
stub = LoadBalancerSlsStub(test_lb_resource)

response = {"success": True, "result": None}

with pytest.raises(ValueError, match="Response marked success but result is None"):
stub._handle_response(response)

def test_handle_response_invalid_base64(self):
"""Test handling of invalid base64 in result."""
stub = LoadBalancerSlsStub(test_lb_resource)

response = {"success": True, "result": "not_valid_base64!!!"}

with pytest.raises(ValueError, match="Failed to deserialize result"):
stub._handle_response(response)


class TestLoadBalancerSlsStubExecuteFunction:
"""Test suite for _execute_function method."""

@pytest.mark.asyncio
async def test_execute_function_no_endpoint_url(self):
"""Test error when endpoint_url is not available."""
mock_resource = MagicMock()
mock_resource.endpoint_url = None
stub = LoadBalancerSlsStub(mock_resource)

request = {"function_name": "test_func", "function_code": "def test_func(): pass"}

with pytest.raises(ValueError, match="Endpoint URL not available"):
await stub._execute_function(request)

@pytest.mark.asyncio
async def test_execute_function_timeout(self):
"""Test timeout error handling."""
mock_resource = MagicMock()
mock_resource.endpoint_url = "http://localhost:8000"
stub = LoadBalancerSlsStub(mock_resource)

request = {"function_name": "test_func", "function_code": "def test_func(): pass"}

import httpx

with patch("tetra_rp.stubs.load_balancer_sls.httpx.AsyncClient") as mock_client:
mock_client.return_value.__aenter__.return_value.post = AsyncMock(
side_effect=httpx.TimeoutException("Timeout")
)

with pytest.raises(TimeoutError, match="Execution timeout"):
await stub._execute_function(request)

@pytest.mark.asyncio
async def test_execute_function_http_error(self):
"""Test HTTP error handling."""
mock_resource = MagicMock()
mock_resource.endpoint_url = "http://localhost:8000"
mock_resource.name = "test-lb"
stub = LoadBalancerSlsStub(mock_resource)

request = {"function_name": "test_func", "function_code": "def test_func(): pass"}

import httpx

mock_response = MagicMock()
mock_response.status_code = 500
mock_response.text = "Internal server error"

with patch("tetra_rp.stubs.load_balancer_sls.httpx.AsyncClient") as mock_client:
error = httpx.HTTPStatusError("Error", request=MagicMock(), response=mock_response)
mock_client.return_value.__aenter__.return_value.post = AsyncMock(
side_effect=error
)

with pytest.raises(RuntimeError, match="HTTP error from endpoint"):
await stub._execute_function(request)


class TestLoadBalancerSlsStubCall:
"""Test suite for __call__ method."""

@pytest.mark.asyncio
async def test_call_success(self):
"""Test successful stub execution."""
mock_resource = MagicMock()
stub = LoadBalancerSlsStub(mock_resource)

def add(x, y):
return x + y

with patch.object(stub, "_execute_function") as mock_execute:
result_b64 = base64.b64encode(cloudpickle.dumps(8)).decode("utf-8")
mock_execute.return_value = {"success": True, "result": result_b64}

result = await stub(add, None, None, True, 5, 3)

assert result == 8
mock_execute.assert_called_once()

@pytest.mark.asyncio
async def test_call_with_dependencies(self):
"""Test stub execution with dependencies."""
mock_resource = MagicMock()
stub = LoadBalancerSlsStub(mock_resource)

def use_requests():
return "success"

deps = ["requests"]

with patch.object(stub, "_execute_function") as mock_execute:
result_b64 = base64.b64encode(cloudpickle.dumps("success")).decode("utf-8")
mock_execute.return_value = {"success": True, "result": result_b64}

result = await stub(use_requests, deps, None, True)

assert result == "success"
# Verify dependencies were included in request
call_args = mock_execute.call_args
request = call_args[0][0]
assert request["dependencies"] == deps