import json
import sys
import time
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent))
from eggfetch.compat.httpx import Client, MockTransport, Request, Response, Timeout
from native_fixtures import local_http_server
QUALIFICATION_MIN_REQUESTS = 50
QUALIFICATION_TIMEOUT_SECONDS = 120
SCHEDULED_MIN_REQUESTS = 500
SCHEDULED_MIN_DURATION_SECONDS = 300
def _mock_handler(request: Request) -> Response:
if request.url.path == "/json":
return Response(200, json={"status": "ok"})
return Response(404)
class TestQualificationChurn:
def test_qualification_churn_sync(self):
transport = MockTransport(_mock_handler)
results = []
errors = []
start = time.monotonic()
with Client(transport=transport, timeout=Timeout(5)) as c:
for i in range(QUALIFICATION_MIN_REQUESTS):
try:
r = c.get("http://test/json")
results.append(r.status_code)
except Exception as exc:
errors.append(str(exc))
elapsed = time.monotonic() - start
assert len(errors) == 0, f"Unexpected errors: {errors}"
assert all(s == 200 for s in results)
assert len(results) >= QUALIFICATION_MIN_REQUESTS
assert elapsed < QUALIFICATION_TIMEOUT_SECONDS
def test_qualification_churn_with_body_reads(self):
transport = MockTransport(_mock_handler)
count = 0
with Client(transport=transport, timeout=Timeout(5)) as c:
for i in range(QUALIFICATION_MIN_REQUESTS):
r = c.get("http://test/json")
assert r.status_code == 200
body = r.content
assert len(body) > 0
count += 1
assert count >= QUALIFICATION_MIN_REQUESTS
def test_qualification_churn_repeated_client_cycles(self):
cycles = 0
for i in range(10):
transport = MockTransport(_mock_handler)
with Client(transport=transport, timeout=Timeout(5)) as c:
for _ in range(5):
r = c.get("http://test/json")
assert r.status_code == 200
cycles += 1
assert cycles >= 10
def test_qualification_churn_post(self):
transport = MockTransport(_mock_handler)
count = 0
with Client(transport=transport, timeout=Timeout(5)) as c:
for i in range(QUALIFICATION_MIN_REQUESTS):
r = c.post("http://test/json", content=b"test payload")
assert r.status_code == 200
count += 1
assert count >= QUALIFICATION_MIN_REQUESTS
def test_sustained_churn_sync(self):
transport = MockTransport(_mock_handler)
total_requests = 0
with Client(transport=transport, timeout=Timeout(5)) as c:
for i in range(100):
r = c.get("http://test/json")
assert r.status_code == 200
body = r.content
assert len(body) > 0
total_requests += 1
assert total_requests == 100, (
f"Expected 100 successful requests, got {total_requests}"
)
def test_sustained_churn_mixed_methods(self):
transport = MockTransport(_mock_handler)
successes = 0
with Client(transport=transport, timeout=Timeout(5)) as c:
for i in range(50):
r = c.get("http://test/json")
assert r.status_code == 200
successes += 1
r = c.post("http://test/json", content=b"payload")
assert r.status_code == 200
successes += 1
assert successes == 100, (
f"Expected 100 successful requests, got {successes}"
)
def test_sustained_churn_repeated_clients(self):
cycles = 0
for i in range(20):
transport = MockTransport(_mock_handler)
with Client(transport=transport, timeout=Timeout(5)) as c:
for _ in range(3):
r = c.get("http://test/json")
assert r.status_code == 200
cycles += 1
assert cycles == 20, (
f"Expected 20 successful client cycles, got {cycles}"
)
def test_sustained_churn_body_content_validation(self):
transport = MockTransport(_mock_handler)
expected_body = json.dumps({"status": "ok"}).encode()
with Client(transport=transport, timeout=Timeout(5)) as c:
for i in range(50):
r = c.get("http://test/json")
assert r.status_code == 200
body = r.content
assert body == expected_body, (
f"Iteration {i}: expected {expected_body!r}, got {body!r}"
)
class TestNativeLoopbackChurn:
def test_loopback_churn_sync(self):
with local_http_server() as (host, port):
url = f"http://{host}:{port}/json"
count = 0
with Client(timeout=Timeout(120)) as c:
for i in range(20):
r = c.get(url)
assert r.status_code == 200
count += 1
assert count >= 20
class TestSoakPolicyBinding:
def test_soak_mode_declaration(self):
mode = {
"mode": "qualification",
"min_requests": QUALIFICATION_MIN_REQUESTS,
"timeout_seconds": QUALIFICATION_TIMEOUT_SECONDS,
"scheduled_min_requests": SCHEDULED_MIN_REQUESTS,
"scheduled_min_duration_seconds": SCHEDULED_MIN_DURATION_SECONDS,
}
assert mode["mode"] == "qualification"
assert mode["min_requests"] > 0
assert mode["scheduled_min_requests"] >= 500
assert mode["scheduled_min_duration_seconds"] >= 300
def test_scheduled_soak_thresholds_declared(self):
assert SCHEDULED_MIN_DURATION_SECONDS >= 300, (
f"Scheduled soak duration must be ≥300s, got {SCHEDULED_MIN_DURATION_SECONDS}"
)
assert SCHEDULED_MIN_REQUESTS >= 500, (
f"Scheduled soak request count must be ≥500, got {SCHEDULED_MIN_REQUESTS}"
)