Files
hse-2026/homework/02-grpc-messenger/tests/test_client.py
T

180 lines
5.6 KiB
Python
Raw Normal View History

2026-09-17 16:34:16 +03:00
import os
import time
import pytest
import requests
HTTP_CONNECT_TIMEOUT_S = 1
HTTP_READ_TIMEOUT_S = 5
HTTP_READY_TIMEOUT_S = 20
HTTP_RETRY_INTERVAL_S = 0.5
HTTP_TIMEOUT = (HTTP_CONNECT_TIMEOUT_S, HTTP_READ_TIMEOUT_S)
MESSAGE_TIMEOUT_S = 10
MESSAGE_POLL_INTERVAL_S = 0.05
def wait_for_http(url):
deadline = time.monotonic() + HTTP_READY_TIMEOUT_S
last_exception = None
while True:
remaining = deadline - time.monotonic()
if remaining <= 0:
pytest.fail(
f'timed out waiting for HTTP endpoint {url}: {last_exception}',
pytrace=False,
)
readiness_timeout = (
min(HTTP_CONNECT_TIMEOUT_S, remaining),
min(HTTP_READ_TIMEOUT_S, remaining),
)
try:
response = requests.get(url, timeout=readiness_timeout)
response.close()
return
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as exc:
last_exception = exc
remaining = deadline - time.monotonic()
if remaining <= 0:
pytest.fail(
f'timed out waiting for HTTP endpoint {url}: {last_exception}',
pytrace=False,
)
time.sleep(min(HTTP_RETRY_INTERVAL_S, remaining))
def post_json(url, path, timeout=HTTP_TIMEOUT, **kwargs):
endpoint = url + path
try:
response = requests.post(endpoint, timeout=timeout, **kwargs)
except requests.exceptions.Timeout as exc:
pytest.fail(f'timed out waiting for HTTP response from {endpoint}: {exc}', pytrace=False)
except requests.exceptions.ConnectionError as exc:
pytest.fail(f'could not connect to HTTP endpoint {endpoint}: {exc}', pytrace=False)
try:
assert response.status_code == 200, response.text
return response.json()
finally:
response.close()
@pytest.fixture(scope='session')
def client1_ready_url():
url = 'http://' + os.environ.get('MESSENGER_TEST_CLIENT1_ADDR', '127.0.0.1:8080')
wait_for_http(url)
return url
@pytest.fixture(scope='session')
def client2_ready_url():
url = 'http://' + os.environ.get('MESSENGER_TEST_CLIENT2_ADDR', '127.0.0.1:8081')
wait_for_http(url)
return url
@pytest.fixture
def client1_url(client1_ready_url):
url = client1_ready_url
get_messages(url) # we need to flush pending messages before and after each tests
yield url
get_messages(url)
@pytest.fixture
def client2_url(client2_ready_url):
url = client2_ready_url
get_messages(url)
yield url
get_messages(url)
def send_message(url, mes):
return post_json(url, '/sendMessage', json=mes)
def get_messages(url, **kwargs):
return post_json(url, '/getAndFlushMessages', **kwargs)
def wait_for_messages(url, expected):
deadline = time.monotonic() + MESSAGE_TIMEOUT_S
messages = []
while len(messages) < len(expected):
remaining = deadline - time.monotonic()
if remaining <= 0:
pytest.fail(
f'timed out waiting for messages from {url}: '
f'expected {expected!r}, received {messages!r}',
pytrace=False,
)
# Divide the remaining budget between connecting and reading the response.
timeout = (
min(HTTP_CONNECT_TIMEOUT_S, remaining / 2),
min(HTTP_READ_TIMEOUT_S, remaining / 2),
)
batch = get_messages(url, timeout=timeout)
assert isinstance(batch, list), f'expected a message array, received {batch!r}'
messages.extend(batch)
assert messages == expected[:len(messages)], \
f'expected {expected!r}, received {messages!r}'
remaining = deadline - time.monotonic()
if remaining <= 0:
pytest.fail(f'timed out waiting for messages from {url}', pytrace=False)
if len(messages) < len(expected):
time.sleep(min(MESSAGE_POLL_INTERVAL_S, remaining))
return messages
def test_single_client_single_message(client1_url, client2_url):
mes = {
'author': 'TestSingleClient',
'text': 'This is test text'
}
resp = send_message(client1_url, mes)
mes['sendTime'] = resp['sendTime']
assert wait_for_messages(client1_url, [mes]) == [mes]
# Drain both subscriptions before the next test sends more messages.
assert wait_for_messages(client2_url, [mes]) == [mes]
def test_single_client_multiple_messages(client1_url, client2_url):
mes = [{
'author': 'TestSingleClient1',
'text': 'This is test text'
}, {
'author': 'TestSingleClient2',
'text': 'This is test text'
}]
for m in mes:
resp = send_message(client1_url, m)
m['sendTime'] = resp['sendTime']
assert wait_for_messages(client1_url, mes) == mes
assert wait_for_messages(client2_url, mes) == mes
def test_two_clients_multiple_messages(client1_url, client2_url):
client1_name = 'TestMultiClient1'
client2_name = 'TestMultiClient2'
mes = [{
'author': client1_name,
'text': 'This is test text #1'
}, {
'author': client1_name,
'text': 'This is test text #2'
}, {
'author': client2_name,
'text': 'This is test text #3'
}, {
'author': client2_name,
'text': 'This is test text #4'
}]
times = set()
for m in mes:
resp = send_message(client1_url if m['author'] == client1_name else client2_url, m)
m['sendTime'] = resp['sendTime']
times.add(m['sendTime'])
assert len(times) == len(mes)
assert wait_for_messages(client1_url, mes) == mes
assert wait_for_messages(client2_url, mes) == mes