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