Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion showtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Circus tent emoji state tracking for Apache Superset ephemeral environments.
"""

__version__ = "0.6.11"
__version__ = "0.6.12"
__author__ = "Maxime Beauchemin"
__email__ = "maximebeauchemin@gmail.com"

Expand Down
3 changes: 2 additions & 1 deletion showtime/core/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,9 @@ def create_environment(
return EnvironmentResult(success=False, error="Service failed to become stable")

# Step 7: Health check the new service (longer timeout for Superset + examples)
# Note: Superset example loading takes more tha 10 mins in some cases
print(f"🏥 Health checking service {service_name}...")
if not self._health_check_service(service_name, max_attempts=20): # 10 minutes total
if not self._health_check_service(service_name, max_attempts=30): # 15 minutes total
return EnvironmentResult(success=False, error="Service failed health checks")

# Step 8: Get IP after health checks pass
Expand Down
59 changes: 59 additions & 0 deletions tests/unit/test_aws_health_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Tests for health check retry behavior.
"""

from unittest.mock import MagicMock, patch

import httpx
import pytest

from showtime.core.aws import AWSInterface


@pytest.fixture
def aws() -> AWSInterface:
"""AWSInterface with mocked clients"""
return AWSInterface(
ecs_client=MagicMock(),
ecr_client=MagicMock(),
ec2_client=MagicMock(),
)


class TestHealthCheckRetries:
"""Core retry behavior tests"""

def test_succeeds_on_healthy_response(self, aws: AWSInterface) -> None:
"""Returns True when health endpoint responds 200"""
with patch.object(aws, "get_environment_ip", return_value="1.2.3.4"):
with patch("httpx.Client") as mock_client:
mock_response = MagicMock(status_code=200)
mock_client.return_value.__enter__.return_value.get.return_value = mock_response

assert aws._health_check_service("test-service", max_attempts=3) is True

def test_retries_and_succeeds(self, aws: AWSInterface) -> None:
"""Retries on failure and succeeds when health check eventually passes"""
with patch.object(aws, "get_environment_ip", return_value="1.2.3.4"):
with patch("httpx.Client") as mock_client:
mock_instance = mock_client.return_value.__enter__.return_value
# Fail twice (health + fallback each), then succeed
mock_instance.get.side_effect = [
httpx.RequestError("refused"), MagicMock(status_code=503), # attempt 1
httpx.RequestError("refused"), MagicMock(status_code=503), # attempt 2
MagicMock(status_code=200), # attempt 3 succeeds
]

with patch("time.sleep"):
assert aws._health_check_service("test-service", max_attempts=5) is True

def test_fails_after_max_attempts_exhausted(self, aws: AWSInterface) -> None:
"""Returns False after all attempts fail"""
with patch.object(aws, "get_environment_ip", return_value="1.2.3.4"):
with patch("httpx.Client") as mock_client:
mock_client.return_value.__enter__.return_value.get.side_effect = (
httpx.RequestError("refused")
)

with patch("time.sleep"):
assert aws._health_check_service("test-service", max_attempts=3) is False