Skip to content

Commit 8fd06fb

Browse files
committed
fix(test): Require actuator health for Spring readiness
1 parent da63abe commit 8fd06fb

File tree

2 files changed

+55
-26
lines changed

2 files changed

+55
-26
lines changed

test/system-test-runner.py

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ def wait_for_spring(self, max_attempts: int = 20) -> bool:
406406
print("Waiting for Spring application to be ready...")
407407

408408
for attempt in range(1, max_attempts + 1):
409+
# All current Spring Boot samples expose actuator/health. Waiting for the
410+
# health endpoint avoids false positives from unrelated services on 8080.
409411
try:
410412
response = requests.head(
411413
"http://localhost:8080/actuator/health",
@@ -418,20 +420,6 @@ def wait_for_spring(self, max_attempts: int = 20) -> bool:
418420
except:
419421
pass
420422

421-
# Fallback: shadow JAR apps may not have actuator endpoints,
422-
# so also try any HTTP connection to confirm the server is up
423-
try:
424-
response = requests.head(
425-
"http://localhost:8080/",
426-
auth=("user", "password"),
427-
timeout=5
428-
)
429-
if response.status_code is not None:
430-
print("Spring application is ready! (actuator not available)")
431-
return True
432-
except:
433-
pass
434-
435423
print(f"Waiting... (attempt {attempt}/{max_attempts})")
436424
time.sleep(1)
437425

@@ -461,18 +449,6 @@ def get_spring_status(self) -> dict:
461449
except:
462450
pass
463451

464-
if not status["http_ready"]:
465-
try:
466-
response = requests.head(
467-
"http://localhost:8080/",
468-
auth=("user", "password"),
469-
timeout=2
470-
)
471-
if response.status_code is not None:
472-
status["http_ready"] = True
473-
except:
474-
pass
475-
476452
return status
477453

478454
def get_sentry_status(self) -> dict:

test/test_system_test_runner.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import importlib.util
2+
from pathlib import Path
3+
import sys
4+
from types import SimpleNamespace
5+
import unittest
6+
from unittest.mock import patch
7+
8+
9+
MODULE_PATH = Path(__file__).resolve().parent / "system-test-runner.py"
10+
SPEC = importlib.util.spec_from_file_location("system_test_runner", MODULE_PATH)
11+
SYSTEM_TEST_RUNNER = importlib.util.module_from_spec(SPEC)
12+
sys.modules.setdefault("requests", SimpleNamespace(head=None))
13+
sys.modules[SPEC.name] = SYSTEM_TEST_RUNNER
14+
assert SPEC.loader is not None
15+
SPEC.loader.exec_module(SYSTEM_TEST_RUNNER)
16+
17+
18+
class SystemTestRunnerTests(unittest.TestCase):
19+
def create_runner(self):
20+
with patch.object(SYSTEM_TEST_RUNNER.SystemTestRunner, "read_pid_file", return_value=None):
21+
return SYSTEM_TEST_RUNNER.SystemTestRunner()
22+
23+
def test_wait_for_spring_requires_actuator_health(self):
24+
runner = self.create_runner()
25+
26+
def fake_head(url, auth, timeout):
27+
self.assertEqual(url, "http://localhost:8080/actuator/health")
28+
return SimpleNamespace(status_code=503)
29+
30+
with patch.object(SYSTEM_TEST_RUNNER.requests, "head", side_effect=fake_head), patch.object(
31+
SYSTEM_TEST_RUNNER.time, "sleep"
32+
):
33+
self.assertFalse(runner.wait_for_spring(max_attempts=1))
34+
35+
def test_get_spring_status_only_reports_http_ready_for_actuator_health(self):
36+
runner = self.create_runner()
37+
runner.spring_server.pid = 123
38+
39+
def fake_head(url, auth, timeout):
40+
self.assertEqual(url, "http://localhost:8080/actuator/health")
41+
return SimpleNamespace(status_code=404)
42+
43+
with patch.object(SYSTEM_TEST_RUNNER.requests, "head", side_effect=fake_head), patch.object(
44+
runner, "is_process_running", return_value=True
45+
):
46+
status = runner.get_spring_status()
47+
48+
self.assertTrue(status["process_running"])
49+
self.assertFalse(status["http_ready"])
50+
51+
52+
if __name__ == "__main__":
53+
unittest.main()

0 commit comments

Comments
 (0)