From 316a4d30afb88f915600d84678efaa965024b2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Wed, 6 May 2026 00:27:47 +0200 Subject: [PATCH 1/6] Fix tests --- tests/conftest.py | 33 ++++++++++++++++++++++++ tests/support/asyncio_utils.py | 47 +++++++++++++++++++++++++++------- tests/test_notifier.py | 19 +++++++------- tests/test_room_api.py | 9 +------ 4 files changed, 82 insertions(+), 26 deletions(-) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..396f62c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,33 @@ +# pylint: disable=missing-class-docstring, missing-function-docstring, missing-module-docstring, redefined-outer-name + +import pytest + +from fishjam import FishjamClient, Room, RoomOptions +from fishjam.errors import HTTPError +from tests.support.env import FISHJAM_ID, FISHJAM_MANAGEMENT_TOKEN + + +class _TrackingFishjamClient(FishjamClient): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._tracked_room_ids: list[str] = [] + + def create_room(self, options: RoomOptions | None = None) -> Room: + room = super().create_room(options) + self._tracked_room_ids.append(room.id) + return room + + def cleanup_tracked_rooms(self) -> None: + for room_id in self._tracked_room_ids: + try: + self.delete_room(room_id) + except HTTPError: + pass + self._tracked_room_ids.clear() + + +@pytest.fixture +def room_api(): + client = _TrackingFishjamClient(FISHJAM_ID, FISHJAM_MANAGEMENT_TOKEN) + yield client + client.cleanup_tracked_rooms() diff --git a/tests/support/asyncio_utils.py b/tests/support/asyncio_utils.py index 36707f9..f399720 100644 --- a/tests/support/asyncio_utils.py +++ b/tests/support/asyncio_utils.py @@ -7,25 +7,54 @@ ASSERTION_TIMEOUT = 15.0 -async def assert_events(notifier: FishjamNotifier, event_checks: list): - await _assert_messages(notifier.on_server_notification, event_checks) - - -async def _assert_messages(notifier_callback, message_checks): +async def assert_events( + notifier: FishjamNotifier, + event_checks: list, + *, + room_id_future: asyncio.Future | None = None, +): + await _assert_messages( + notifier.on_server_notification, event_checks, room_id_future + ) + + +async def _assert_messages(notifier_callback, message_checks, room_id_future): success_event = asyncio.Event() + pending: list = [] + room_id_holder: dict = {"value": None, "set": False} - @notifier_callback - def handle_message(message): + def _consume(message): if len(message_checks) > 0: expected_msg = message_checks[0] if message == expected_msg or isinstance(message, expected_msg): message_checks.pop(0) - if message_checks == []: success_event.set() + @notifier_callback + def handle_message(message): + if not room_id_holder["set"]: + pending.append(message) + return + + expected_room_id = room_id_holder["value"] + if expected_room_id is not None: + if getattr(message, "room_id", None) != expected_room_id: + return + + _consume(message) + + async def _wait_for_success(): + if room_id_future is not None: + room_id_holder["value"] = await room_id_future + room_id_holder["set"] = True + for msg in pending: + handle_message(msg) + pending.clear() + await success_event.wait() + try: - await asyncio.wait_for(success_event.wait(), ASSERTION_TIMEOUT) + await asyncio.wait_for(_wait_for_success(), ASSERTION_TIMEOUT) except asyncio.exceptions.TimeoutError as exc: raise asyncio.exceptions.TimeoutError( f"{message_checks[0]} hasn't been received within timeout" diff --git a/tests/test_notifier.py b/tests/test_notifier.py index 9dd93aa..5c31908 100644 --- a/tests/test_notifier.py +++ b/tests/test_notifier.py @@ -93,11 +93,6 @@ def handle_notitifcation(_notification): await asyncio.gather(notifier_task, return_exceptions=True) -@pytest.fixture -def room_api(): - return FishjamClient(FISHJAM_ID, FISHJAM_MANAGEMENT_TOKEN) - - @pytest.fixture def notifier(): notifier = FishjamNotifier( @@ -115,8 +110,9 @@ async def test_room_created_deleted( ): event_checks = [ServerMessageRoomCreated, ServerMessageRoomDeleted] + room_id_future: asyncio.Future = asyncio.get_event_loop().create_future() assert_task = asyncio.ensure_future( - assert_events(notifier, event_checks.copy()) + assert_events(notifier, event_checks.copy(), room_id_future=room_id_future) ) notifier_task = asyncio.ensure_future(notifier.connect()) try: @@ -124,6 +120,7 @@ async def test_room_created_deleted( options = RoomOptions(webhook_url=WEBHOOK_URL) room = room_api.create_room(options=options) + room_id_future.set_result(room.id) room_api.delete_room(room.id) @@ -148,8 +145,9 @@ async def test_peer_connected_disconnected( ServerMessageRoomDeleted, ] + room_id_future: asyncio.Future = asyncio.get_event_loop().create_future() assert_task = asyncio.ensure_future( - assert_events(notifier, event_checks.copy()) + assert_events(notifier, event_checks.copy(), room_id_future=room_id_future) ) notifier_task = asyncio.ensure_future(notifier.connect()) tasks = [assert_task, notifier_task] @@ -158,6 +156,7 @@ async def test_peer_connected_disconnected( options = RoomOptions(webhook_url=WEBHOOK_URL) room = room_api.create_room(options=options) + room_id_future.set_result(room.id) peer, token = room_api.create_peer(room.id) peer_socket = PeerSocket(fishjam_url=FISHJAM_ID) @@ -189,8 +188,9 @@ async def test_peer_connected_room_deleted( ServerMessageRoomDeleted, ] + room_id_future: asyncio.Future = asyncio.get_event_loop().create_future() assert_task = asyncio.ensure_future( - assert_events(notifier, event_checks.copy()) + assert_events(notifier, event_checks.copy(), room_id_future=room_id_future) ) notifier_task = asyncio.ensure_future(notifier.connect()) tasks = [assert_task, notifier_task] @@ -199,6 +199,7 @@ async def test_peer_connected_room_deleted( options = RoomOptions(webhook_url=WEBHOOK_URL) room = room_api.create_room(options=options) + room_id_future.set_result(room.id) _peer, token = room_api.create_peer(room.id) peer_socket = PeerSocket(fishjam_url=FISHJAM_ID) @@ -217,7 +218,7 @@ async def test_peer_connected_room_deleted( self.assert_webhook_events(event_checks, event_queue, room.id) - def assert_webhook_events(self, event_checks, event_queue, room_id, timeout=60): + def assert_webhook_events(self, event_checks, event_queue, room_id, timeout=15): deadline = time.monotonic() + timeout received = [] diff --git a/tests/test_room_api.py b/tests/test_room_api.py index 2416e34..d010e2f 100644 --- a/tests/test_room_api.py +++ b/tests/test_room_api.py @@ -45,9 +45,7 @@ def test_invalid_token(self): with pytest.raises(UnauthorizedError): room_api.create_room() - def test_valid_token(self): - room_api = FishjamClient(FISHJAM_ID, FISHJAM_MANAGEMENT_TOKEN) - + def test_valid_token(self, room_api: FishjamClient): room = room_api.create_room() all_rooms = room_api.get_all_rooms() @@ -84,11 +82,6 @@ def mock_send(request, **kwargs): assert captured_headers["x-fishjam-api-client"] == expected_header_value -@pytest.fixture -def room_api(): - return FishjamClient(FISHJAM_ID, FISHJAM_MANAGEMENT_TOKEN) - - class TestCreateRoom: def test_no_params(self, room_api: FishjamClient): room = room_api.create_room() From 72df1e72ec46eecf9351c8bec8d6437d10533bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Wed, 6 May 2026 00:38:06 +0200 Subject: [PATCH 2/6] Increase localtunnel timeout --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdf254a..c768b5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,7 +74,7 @@ jobs: npm exec localtunnel -- --port 5000 > tunnel.log 2>&1 & # Poll for the URL - TIMEOUT=15 + TIMEOUT=90 ELAPSED=0 echo "Waiting for localtunnel to generate URL..." From 3383dca877742b468ea897f919df00104f632c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Wed, 6 May 2026 00:44:27 +0200 Subject: [PATCH 3/6] Increase webhook timeout --- .github/workflows/ci.yml | 1 + tests/test_notifier.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c768b5b..96ddaaf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: type-check: runs-on: ubuntu-latest strategy: &strategy + fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: diff --git a/tests/test_notifier.py b/tests/test_notifier.py index 5c31908..3370361 100644 --- a/tests/test_notifier.py +++ b/tests/test_notifier.py @@ -184,6 +184,7 @@ async def test_peer_connected_room_deleted( ServerMessageRoomCreated, ServerMessagePeerAdded, ServerMessagePeerConnected, + ServerMessagePeerDisconnected, ServerMessagePeerDeleted, ServerMessageRoomDeleted, ] @@ -218,7 +219,7 @@ async def test_peer_connected_room_deleted( self.assert_webhook_events(event_checks, event_queue, room.id) - def assert_webhook_events(self, event_checks, event_queue, room_id, timeout=15): + def assert_webhook_events(self, event_checks, event_queue, room_id, timeout=60): deadline = time.monotonic() + timeout received = [] From 9508596a53aeb5fc7039fd6b66bbad532c61c806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Wed, 6 May 2026 09:43:22 +0200 Subject: [PATCH 4/6] use INSTATUNNEL --- .github/workflows/ci.yml | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96ddaaf..6ef4b6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,10 +22,10 @@ jobs: type-check: runs-on: ubuntu-latest - strategy: &strategy + strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: &python-versions ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 @@ -52,7 +52,11 @@ jobs: test: runs-on: ubuntu-latest needs: [lint-and-format] - strategy: *strategy + strategy: + fail-fast: false + max-parallel: 1 + matrix: + python-version: *python-versions steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v6 @@ -68,20 +72,19 @@ jobs: uv sync --locked --all-extras --all-packages fi - - name: Initialize Localtunnel + - name: Initialize InstaTunnel id: tunnel run: | - npm install -g localtunnel - npm exec localtunnel -- --port 5000 > tunnel.log 2>&1 & + npm install -g instatunnel + nohup instatunnel 5000 > tunnel.log 2>&1 & - # Poll for the URL - TIMEOUT=90 + TIMEOUT=60 ELAPSED=0 - echo "Waiting for localtunnel to generate URL..." + echo "Waiting for InstaTunnel to generate URL..." - while ! grep -q "https://" tunnel.log; do + while ! grep -qE 'https://[^ ]+\.instatunnel\.my' tunnel.log; do if [ $ELAPSED -ge $TIMEOUT ]; then - echo "Error: Localtunnel timed out after ${TIMEOUT}s" + echo "Error: InstaTunnel timed out after ${TIMEOUT}s" cat tunnel.log exit 1 fi @@ -89,15 +92,15 @@ jobs: ELAPSED=$((ELAPSED + 1)) done - TUNNEL_URL=$(grep -o 'https://[^ ]*' tunnel.log | head -n 1) + TUNNEL_URL=$(grep -oE 'https://[^ ]+\.instatunnel\.my' tunnel.log | head -n 1) echo "url=$TUNNEL_URL" >> $GITHUB_OUTPUT - echo "Localtunnel is live at: $TUNNEL_URL" + echo "InstaTunnel is live at: $TUNNEL_URL" - - name: Upload localtunnel log + - name: Upload InstaTunnel log if: always() uses: actions/upload-artifact@v4 with: - name: localtunnel-log-py${{ matrix.python-version }} + name: instatunnel-log-py${{ matrix.python-version }} path: tunnel.log - name: Run tests From 7c47d5b82d9858983ddb84695d021888e08e6caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Wed, 6 May 2026 11:52:50 +0200 Subject: [PATCH 5/6] 3.14 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ef4b6b..ecbea51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: &python-versions ["3.10", "3.11", "3.12", "3.13"] + python-version: &python-versions ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 From 76a6a4f816cfa8798a675d7dbc9deac1f2e3ce05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Wed, 6 May 2026 13:02:57 +0200 Subject: [PATCH 6/6] get_running_loop --- tests/test_notifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_notifier.py b/tests/test_notifier.py index 3370361..7ff7727 100644 --- a/tests/test_notifier.py +++ b/tests/test_notifier.py @@ -110,7 +110,7 @@ async def test_room_created_deleted( ): event_checks = [ServerMessageRoomCreated, ServerMessageRoomDeleted] - room_id_future: asyncio.Future = asyncio.get_event_loop().create_future() + room_id_future: asyncio.Future = asyncio.get_running_loop().create_future() assert_task = asyncio.ensure_future( assert_events(notifier, event_checks.copy(), room_id_future=room_id_future) ) @@ -145,7 +145,7 @@ async def test_peer_connected_disconnected( ServerMessageRoomDeleted, ] - room_id_future: asyncio.Future = asyncio.get_event_loop().create_future() + room_id_future: asyncio.Future = asyncio.get_running_loop().create_future() assert_task = asyncio.ensure_future( assert_events(notifier, event_checks.copy(), room_id_future=room_id_future) ) @@ -189,7 +189,7 @@ async def test_peer_connected_room_deleted( ServerMessageRoomDeleted, ] - room_id_future: asyncio.Future = asyncio.get_event_loop().create_future() + room_id_future: asyncio.Future = asyncio.get_running_loop().create_future() assert_task = asyncio.ensure_future( assert_events(notifier, event_checks.copy(), room_id_future=room_id_future) )