From 5b986d2f122765bc206c16f468a6b509f8f9c79f Mon Sep 17 00:00:00 2001 From: m3rchant-man Date: Fri, 31 Oct 2025 12:32:03 -0400 Subject: [PATCH 1/3] refactor: Update test scripts to default to verbose and added new integration tests for websockets and queue matching --- _scripts/run_tests.sh | 4 +- tests/requirements.txt | 2 + tests/test_api.py | 40 ------------------ tests/test_auth.py | 45 ++++++++++++++++++++ tests/test_queue.py | 94 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 42 deletions(-) create mode 100644 tests/test_auth.py create mode 100644 tests/test_queue.py diff --git a/_scripts/run_tests.sh b/_scripts/run_tests.sh index eb8a0cc..1ec35a8 100755 --- a/_scripts/run_tests.sh +++ b/_scripts/run_tests.sh @@ -1,4 +1,4 @@ -#!/bin/bash + #!/bin/bash set -eo pipefail SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) @@ -43,4 +43,4 @@ fi source "$VENV_PATH/bin/activate" echo "Running integration tests..." -pytest "$PROJECT_ROOT/tests/" "$@" +pytest -v "$PROJECT_ROOT/tests/" "$@" diff --git a/tests/requirements.txt b/tests/requirements.txt index 3363813..f9728ac 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,2 +1,4 @@ pytest==8.2.0 requests==2.31.0 +websockets==12.0 +pytest-asyncio==0.23.6 diff --git a/tests/test_api.py b/tests/test_api.py index a135a05..f0cd55d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,24 +1,9 @@ -import pytest import requests -import uuid import os BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8080") -@pytest.fixture -def unique_user_payload(): - """ - Pytest fixture to generate a unique user payload for registration. - """ - unique_id = uuid.uuid4() - return { - "username": f"testuser_{unique_id}", - "password": f"password_{uuid.uuid4()}", - "email": f"test_{unique_id}@example.com", - } - - def test_health_check(): """ Tests if the /health endpoint is working. @@ -26,28 +11,3 @@ def test_health_check(): response = requests.get(f"{BASE_URL}/health") assert response.status_code == 200 assert response.text == "OK" - - -def test_user_registration_success(unique_user_payload): - """ - Tests successful user registration. - """ - response = requests.post(f"{BASE_URL}/register", json=unique_user_payload) - assert response.status_code == 201 - response_json = response.json() - assert response_json["status"] == "success" - assert response_json["message"] == "User registered successfully" - - -def test_user_registration_failure_duplicate_user(unique_user_payload): - """ - Tests that registration fails if the user already exists. - """ - # First registration should succeed - response1 = requests.post(f"{BASE_URL}/register", json=unique_user_payload) - assert response1.status_code == 201 - - # Second registration with the same data should fail - response2 = requests.post(f"{BASE_URL}/register", json=unique_user_payload) - assert response2.status_code == 409 # Conflict - assert "Username or email already exists" in response2.text diff --git a/tests/test_auth.py b/tests/test_auth.py new file mode 100644 index 0000000..e31ad88 --- /dev/null +++ b/tests/test_auth.py @@ -0,0 +1,45 @@ +import pytest +import requests +import uuid +import os + +BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8080") +WS_URL = f"ws://{BASE_URL.split('//')[1]}/ws" if BASE_URL.startswith("http") else f"ws://{BASE_URL}/ws" + + +@pytest.fixture +def unique_user_payload(): + """ + Pytest fixture to generate a unique user payload for registration. + """ + unique_id = uuid.uuid4() + return { + "username": f"testuser_{unique_id}", + "password": f"password_{unique_id}", + "email": f"test_{unique_id}@example.com", + } + + +def test_user_registration_success(unique_user_payload): + """ + Tests successful user registration via HTTP. + """ + response = requests.post(f"{BASE_URL}/register", json=unique_user_payload) + assert response.status_code == 201 + response_json = response.json() + assert response_json["status"] == "success" + assert response_json["message"] == "User registered successfully" + + +def test_user_registration_failure_duplicate_user(unique_user_payload): + """ + Tests that registration fails if the user already exists via HTTP. + """ + # First registration should succeed + response1 = requests.post(f"{BASE_URL}/register", json=unique_user_payload) + assert response1.status_code == 201 + + # Second registration with the same data should fail + response2 = requests.post(f"{BASE_URL}/register", json=unique_user_payload) + assert response2.status_code == 409 # Conflict + assert "Username or email already exists" in response2.text diff --git a/tests/test_queue.py b/tests/test_queue.py new file mode 100644 index 0000000..5b84577 --- /dev/null +++ b/tests/test_queue.py @@ -0,0 +1,94 @@ +import requests +import pytest +import uuid +import os +import asyncio +import websockets +import json + +BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8080") +WS_URL = f"ws://{BASE_URL.split('//')[1]}/ws" if BASE_URL.startswith("http") else f"ws://{BASE_URL}/ws" + + +async def register_and_get_token(username, password, email): + """Helper function to register a user via WebSocket and get a token.""" + async with websockets.connect(WS_URL, open_timeout=30) as websocket: + payload = {"username": username, "password": password, "email": email} + await websocket.send(json.dumps({"type": "register", "payload": payload})) + + response = await asyncio.wait_for(websocket.recv(), timeout=30) + data = json.loads(response) + + assert data["type"] == "register_success" + assert "token" in data["payload"] + return data["payload"]["token"] + + +async def player_flow(token): + """Simulates a player connecting, logging in, and joining the queue.""" + # Increased connection timeout + async with websockets.connect(WS_URL, open_timeout=30) as websocket: + # Login with token + await websocket.send(json.dumps({"type": "token_auth", "payload": {"token": token}})) + + # Increased receive timeout + login_response = await asyncio.wait_for(websocket.recv(), timeout=30) + login_data = json.loads(login_response) + assert login_data["type"] == "auth_success" + + # Join queue + await websocket.send(json.dumps({"type": "join_queue"})) + + # Increased receive timeout + queue_response = await asyncio.wait_for(websocket.recv(), timeout=30) + assert json.loads(queue_response) == {"type": "queued"} + + # Wait for match + # Increased timeout for match response + match_response = await asyncio.wait_for(websocket.recv(), timeout=40) + match_data = json.loads(match_response) + assert match_data["type"] == "match_found", \ + f"Expected match_found, got {match_data.get('type')}. Full payload: {match_data}" + return match_data + +@pytest.mark.asyncio +async def test_queue_and_match(): + """ + Tests queuing two players and verifying they get matched. + """ + # Generate unique users + uid1 = uuid.uuid4() + user1_username = f"testuser_{uid1}" + user1_password = f"password_{uid1}" + user1_email = f"test_{uid1}@example.com" + + uid2 = uuid.uuid4() + user2_username = f"testuser_{uid2}" + user2_password = f"password_{uid2}" + user2_email = f"test_{uid2}@example.com" + + # Register users via WebSocket and get tokens + token1 = await register_and_get_token(user1_username, user1_password, user1_email) + token2 = await register_and_get_token(user2_username, user2_password, user2_email) + + # Run player flows concurrently + task1 = asyncio.create_task(player_flow(token1)) + await asyncio.sleep(1) # a small delay to ensure player 1 joins queue first this is set too high TODO fix + task2 = asyncio.create_task(player_flow(token2)) + + match_data1, match_data2 = await asyncio.gather(task1, task2) + + # Verify match details + # Note: Since the server doesn't tell us our own username back on match, + # we can't easily assert the opponent's name without more complex state tracking. + + assert "players" in match_data1["payload"] + assert "players" in match_data2["payload"] + assert match_data1["payload"]["port"] == match_data2["payload"]["port"] + assert "containerID" in match_data1["payload"] + assert "containerID" in match_data2["payload"] + + # Check that the players in the match are the correct ones + expected_players = {user1_username, user2_username} + assert set(match_data1["payload"]["players"]) == expected_players + assert set(match_data2["payload"]["players"]) == expected_players From 43a1783426ea61911d5e9ca6e986d5c82ebcc794 Mon Sep 17 00:00:00 2001 From: m3rchant-man Date: Fri, 31 Oct 2025 12:39:26 -0400 Subject: [PATCH 2/3] refactor: Added new integration tests for websockets and queue match --- tests/test_auth.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/test_auth.py b/tests/test_auth.py index e31ad88..c3d9636 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -2,6 +2,9 @@ import requests import uuid import os +import asyncio +import websockets +import json BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8080") WS_URL = f"ws://{BASE_URL.split('//')[1]}/ws" if BASE_URL.startswith("http") else f"ws://{BASE_URL}/ws" @@ -43,3 +46,38 @@ def test_user_registration_failure_duplicate_user(unique_user_payload): response2 = requests.post(f"{BASE_URL}/register", json=unique_user_payload) assert response2.status_code == 409 # Conflict assert "Username or email already exists" in response2.text + + +@pytest.mark.asyncio +async def test_user_registration_and_authentication(unique_user_payload): + """ + Tests that a user can be created and then authenticated via WebSocket. + """ + # Register user via WebSocket to get a token + async with websockets.connect(WS_URL) as websocket: + register_payload = { + "type": "register", + "payload": unique_user_payload + } + await websocket.send(json.dumps(register_payload)) + + response_str = await websocket.recv() + response_data = json.loads(response_str) + + assert response_data.get("type") == "register_success" + token = response_data.get("payload", {}).get("token") + assert token is not None + + # Authenticate with the token in a new connection + async with websockets.connect(WS_URL) as websocket: + token_auth_payload = { + "type": "token_auth", + "payload": {"token": token}, + } + await websocket.send(json.dumps(token_auth_payload)) + + response_str = await websocket.recv() + response_data = json.loads(response_str) + + assert response_data["type"] == "auth_success" + assert "token" in response_data["payload"] From f1cde850e2b3fdc5016b3b6e8152b7642d1e0f70 Mon Sep 17 00:00:00 2001 From: m3rchant-man Date: Fri, 31 Oct 2025 13:04:04 -0400 Subject: [PATCH 3/3] fixed spacing artifact fixed spacing artifact --- _scripts/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_scripts/run_tests.sh b/_scripts/run_tests.sh index 1ec35a8..8c90712 100755 --- a/_scripts/run_tests.sh +++ b/_scripts/run_tests.sh @@ -1,4 +1,4 @@ - #!/bin/bash +#!/bin/bash set -eo pipefail SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)