Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +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", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
Expand All @@ -51,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
Comment thread
roznawsk marked this conversation as resolved.
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
Expand All @@ -67,36 +72,35 @@ 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=15
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
sleep 1
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
Expand Down
33 changes: 33 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -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()
Comment thread
roznawsk marked this conversation as resolved.


@pytest.fixture
def room_api():
client = _TrackingFishjamClient(FISHJAM_ID, FISHJAM_MANAGEMENT_TOKEN)
yield client
client.cleanup_tracked_rooms()
47 changes: 38 additions & 9 deletions tests/support/asyncio_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 10 additions & 8 deletions tests/test_notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -115,15 +110,17 @@ async def test_room_created_deleted(
):
event_checks = [ServerMessageRoomCreated, ServerMessageRoomDeleted]

room_id_future: asyncio.Future = asyncio.get_running_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:
await notifier.wait_ready()

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)

Expand All @@ -148,8 +145,9 @@ async def test_peer_connected_disconnected(
ServerMessageRoomDeleted,
]

room_id_future: asyncio.Future = asyncio.get_running_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]
Expand All @@ -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)
Expand Down Expand Up @@ -185,12 +184,14 @@ async def test_peer_connected_room_deleted(
ServerMessageRoomCreated,
ServerMessagePeerAdded,
ServerMessagePeerConnected,
ServerMessagePeerDisconnected,
ServerMessagePeerDeleted,
ServerMessageRoomDeleted,
]

room_id_future: asyncio.Future = asyncio.get_running_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]
Expand All @@ -199,6 +200,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)
Expand Down
9 changes: 1 addition & 8 deletions tests/test_room_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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()
Expand Down
Loading