Skip to content

Commit 927d3ae

Browse files
committed
Fix tests
1 parent b889565 commit 927d3ae

File tree

2 files changed

+90
-58
lines changed

2 files changed

+90
-58
lines changed

tests/conftest.py

Lines changed: 81 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import sys
32
import time
43
import httpx
54
import pytest
@@ -13,6 +12,11 @@
1312
log = get_logger('setup-tests')
1413

1514

15+
@pytest.fixture(scope="session")
16+
def anyio_backend():
17+
return 'asyncio'
18+
19+
1620
def find_free_port():
1721
"""Find a free port on localhost."""
1822
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
@@ -21,74 +25,100 @@ def find_free_port():
2125
port = s.getsockname()[1]
2226
return port
2327

28+
def is_redis_reachable(tries: int = 5, delay: float = 1.0) -> bool:
29+
"""Check if Redis server is reachable."""
30+
for _ in range(tries):
31+
try:
32+
redis_client.from_url('redis://127.0.0.1:6379').ping()
33+
return True
34+
except redis_client.ConnectionError:
35+
time.sleep(delay)
36+
return False
2437

25-
@pytest.fixture(scope="session")
26-
def anyio_backend():
27-
return 'asyncio'
28-
38+
def is_uvicorn_reachable(port: int, tries: int = 5, delay: float = 1.0, *, base_url: str = 'http://127.0.0.1') -> bool:
39+
"""Check if Uvicorn server is reachable."""
40+
for _ in range(tries):
41+
try:
42+
response = httpx.get(f'{base_url}:{port}/health', timeout=delay * 0.8)
43+
if response.status_code == 200:
44+
return True
45+
except httpx.RequestError:
46+
time.sleep(delay)
47+
return False
2948

30-
@pytest.fixture(scope="session")
31-
def live_server():
32-
"""Start uvicorn server in a subprocess."""
33-
port = find_free_port()
34-
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
35-
processes = {}
49+
def start_redis_server(project_root: str) -> None:
50+
print('\n', end='\r')
51+
log.debug("- Starting Redis server...")
3652

3753
try:
38-
print()
39-
log.debug(f"Starting test server...")
4054
redis_proc = subprocess.Popen(
4155
['redis-server', '--port', '6379', '--save', '', '--appendonly', 'no'],
4256
cwd=project_root,
4357
stdout=subprocess.DEVNULL
4458
)
45-
processes['redis'] = redis_proc
4659

47-
try:
48-
time.sleep(2)
49-
redis_client.from_url('redis://127.0.0.1:6379').ping()
50-
except redis_client.ConnectionError:
51-
log.error("Failed to connect to Redis server. Ensure Redis is running.")
52-
raise
60+
if not is_redis_reachable():
61+
log.error("x Redis server did not start successfully.")
62+
redis_proc.terminate()
63+
raise RuntimeError("Could not start Redis server for tests.") from None
64+
65+
return redis_proc
5366

67+
except Exception as e:
68+
log.error("x Failed to start Redis server.", exc_info=e)
69+
redis_proc.kill()
70+
raise RuntimeError("Could not start Redis server for tests.") from e
71+
72+
def start_uvicorn_server(port: int, project_root: str) -> None:
73+
try:
74+
log.debug("- Starting uvicorn server...")
5475
uvicorn_proc = subprocess.Popen(
5576
['uvicorn', 'app:app', '--host', '127.0.0.1', '--port', str(port)],
5677
cwd=project_root
5778
)
79+
80+
if not is_uvicorn_reachable(port):
81+
log.error("x Uvicorn server did not start successfully.")
82+
uvicorn_proc.terminate()
83+
raise RuntimeError("Could not start Uvicorn server for tests.") from None
84+
85+
return uvicorn_proc
86+
87+
except Exception as e:
88+
log.error("x Failed to start Uvicorn server.", exc_info=e)
89+
uvicorn_proc.kill()
90+
raise RuntimeError("Could not start Uvicorn server for tests.") from e
91+
92+
@pytest.fixture(scope="session")
93+
def live_server():
94+
"""Start uvicorn server in a subprocess."""
95+
port = find_free_port()
96+
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
97+
processes = {}
98+
99+
if not is_redis_reachable(tries=1):
100+
redis_proc = start_redis_server(project_root)
101+
processes['redis'] = redis_proc
102+
103+
if not is_uvicorn_reachable(port, tries=1):
104+
uvicorn_proc = start_uvicorn_server(port, project_root)
58105
processes['uvicorn'] = uvicorn_proc
59106

60-
base_url = f'127.0.0.1:{port}'
61-
max_retries = 5
62-
for i in range(max_retries):
63-
try:
64-
response = httpx.get(f'http://{base_url}/health', timeout=5)
65-
if response.status_code == 200:
66-
break
67-
except Exception as e:
68-
if i == max_retries - 1:
69-
uvicorn_proc.terminate()
70-
raise RuntimeError(f"Server failed to start after {max_retries} attempts") from None
71-
72-
time.sleep(2.0)
73-
74-
log.debug(f"Server started at {base_url}")
75-
print()
76-
yield base_url
107+
yield f'127.0.0.1:{port}'
77108

78-
print()
79-
for name, process in sorted(processes.items(), key=lambda x: -ord(x[0][0])):
80-
if process.poll() is None:
81-
log.debug(f"Terminating {name} process")
82-
process.terminate()
83-
try:
84-
process.wait(timeout=5)
85-
except subprocess.TimeoutExpired: pass
86-
87-
finally:
88-
for name, process in processes.items():
89-
if process.poll() is None:
90-
log.warning(f"Forcefully terminating {name}")
91-
process.kill()
109+
print()
110+
for name in ['uvicorn', 'redis']:
111+
process = processes.get(name)
112+
if not process or process.poll() is not None:
113+
continue
114+
115+
log.debug(f"- Terminating {name} process")
116+
process.terminate()
117+
try:
118+
process.wait(timeout=5)
119+
except subprocess.TimeoutExpired:
120+
log.warning(f"- {name} process did not terminate in time, killing it")
121+
process.kill()
92122

93123

94124
@pytest.fixture

views/websockets.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import warnings
44
from fastapi import WebSocket, APIRouter, WebSocketDisconnect, BackgroundTasks
55
from fastapi.responses import PlainTextResponse
6-
from starlette.websockets import WebSocketClose
76
from pydantic import ValidationError
87

98
from lib.logging import get_logger
@@ -23,11 +22,9 @@ async def websocket_upload(websocket: WebSocket, uid: str):
2322
Then, the client must wait for the signal before sending file chunks.
2423
"""
2524
if any(char not in string.ascii_letters + string.digits + '-' for char in uid):
26-
await websocket.send_denial_response(PlainTextResponse(
27-
"Invalid transfer ID. Must only contain alphanumeric characters and hyphens.",
28-
status_code=400
29-
))
30-
return WebSocketClose(code=1006, reason="Invalid transfer ID")
25+
log.debug(f"△ Invalid transfer ID: {uid}")
26+
await websocket.close(code=1008, reason="Invalid transfer ID")
27+
return
3128

3229
await websocket.accept()
3330
log.debug(f"△ Websocket upload request.")
@@ -64,10 +61,15 @@ async def websocket_upload(websocket: WebSocket, uid: str):
6461
log.warning("△ Receiver did not connect in time.")
6562
await websocket.send_text(f"Error: Receiver did not connect in time.")
6663
return
64+
except Exception as e:
65+
log.error("△ Error while waiting for receiver connection.", exc_info=e)
66+
await websocket.send_text("Error: Error while waiting for receiver connection.")
67+
return
6768

68-
transfer.info("△ Starting upload...")
69+
transfer.debug("△ Sending go-ahead...")
6970
await websocket.send_text("Go for file chunks")
7071

72+
transfer.info("△ Starting upload...")
7173
await transfer.collect_upload(
7274
stream=websocket.iter_bytes(),
7375
on_error=send_error_and_close(websocket),

0 commit comments

Comments
 (0)