From b585f681dabe1aa7a6db5f7bc5852996a29b4dfe Mon Sep 17 00:00:00 2001 From: voluntas Date: Sun, 10 Nov 2024 19:04:41 +0900 Subject: [PATCH 01/11] =?UTF-8?q?authz=20=E3=81=AB=E3=82=88=E3=82=8B?= =?UTF-8?q?=E6=89=95=E3=81=84=E5=87=BA=E3=81=97=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 1 + test_with_llvm.py | 41 -------------------- tests/test_authz.py | 66 +++++++++++++++++++++++++++++++++ tests/test_sendonly_recvonly.py | 11 ++---- uv.lock | 11 ++++++ 5 files changed, 82 insertions(+), 48 deletions(-) delete mode 100644 test_with_llvm.py create mode 100644 tests/test_authz.py diff --git a/pyproject.toml b/pyproject.toml index db34351..223be69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,7 @@ dev-dependencies = [ "pytest", "ruff", "mypy", + "pyjwt>=2.9.0", ] [tool.ruff] diff --git a/test_with_llvm.py b/test_with_llvm.py deleted file mode 100644 index 59e371c..0000000 --- a/test_with_llvm.py +++ /dev/null @@ -1,41 +0,0 @@ -import sys -import time - -import lldb - - -def test(debugger, command, result, internal_dict): - debugger.HandleCommand("settings set target.process.follow-fork-mode child") - - target = debugger.CreateTargetWithFileAndArch("uv", lldb.LLDB_ARCH_DEFAULT) - process = target.LaunchSimple( - ["run", "pytest", "tests/test_sora_disconnect.py", "-s"], None, None - ) - - if not process: - print("Error: could not launch process") - return - - while True: - time.sleep(1.0) - state = process.GetState() - - if state == lldb.eStateExited: - exit_status = process.GetExitStatus() - # debugger.HandleCommand(f"exit {exit_status}") - # sys.exit(exit_status) - debugger.HandleCommand("exit 0") - sys.exit(0) - elif state == lldb.eStateStopped: - thread = process.GetSelectedThread() - if thread.GetStopReason() == lldb.eStopReasonExec: - process.Continue() - continue - debugger.HandleCommand("bt all") - debugger.HandleCommand("exit 1") - sys.exit(1) - - -# LLDBにスクリプトを初期化してコマンドを追加 -def __lldb_init_module(debugger, internal_dict): - debugger.HandleCommand("command script add -f test_with_llvm.test test") diff --git a/tests/test_authz.py b/tests/test_authz.py new file mode 100644 index 0000000..ef55c76 --- /dev/null +++ b/tests/test_authz.py @@ -0,0 +1,66 @@ +import sys +import time +import uuid + +import jwt +import pytest +from client import SoraClient, SoraRole + + +@pytest.mark.parametrize( + "video_codec_params", + [ + # video_codec, encoder_implementation, decoder_implementation + ("VP8", "libvpx", "libvpx"), + ("VP9", "libvpx", "libvpx"), + ("AV1", "libaom", "dav1d"), + ], +) +def test_sendonly_authz_video_codec_type(setup, video_codec_params): + video_codec, encoder_implementation, decoder_implementation = video_codec_params + + signaling_urls = setup.get("signaling_urls") + channel_id_prefix = setup.get("channel_id_prefix") + secret = setup.get("secret") + + channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}" + + access_token = jwt.encode( + { + "channel_id": channel_id, + "video": True, + "video_codec_type": video_codec, + }, + secret, + algorithm="HS256", + ) + metadata = {"access_token": access_token} + + sendonly = SoraClient( + signaling_urls, + SoraRole.SENDONLY, + channel_id, + audio=False, + video=True, + metadata=metadata, + ) + sendonly.connect(fake_video=True) + + time.sleep(5) + + assert sendonly.offer_message is not None + print(sendonly.offer_message) + + sendonly_stats = sendonly.get_stats() + + sendonly.disconnect() + + # codec が無かったら StopIteration 例外が上がる + sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec") + assert sendonly_codec_stats["mimeType"] == f"video/{video_codec}" + + # outbound-rtp が無かったら StopIteration 例外が上がる + outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp") + assert outbound_rtp_stats["encoderImplementation"] == encoder_implementation + assert outbound_rtp_stats["bytesSent"] > 0 + assert outbound_rtp_stats["packetsSent"] > 0 diff --git a/tests/test_sendonly_recvonly.py b/tests/test_sendonly_recvonly.py index eecdf31..89a2f99 100644 --- a/tests/test_sendonly_recvonly.py +++ b/tests/test_sendonly_recvonly.py @@ -60,18 +60,15 @@ def test_sendonly_recvonly_audio(setup): assert inbound_rtp_stats["packetsReceived"] > 0 -@pytest.fixture( - params=[ +@pytest.mark.parametrize( + "video_codec_params", + [ # video_codec, encoder_implementation, decoder_implementation ("VP8", "libvpx", "libvpx"), ("VP9", "libvpx", "libvpx"), ("AV1", "libaom", "dav1d"), - ] + ], ) -def video_codec_params(request): - return request.param - - def test_sendonly_recvonly_video(setup, video_codec_params): video_codec, encoder_implementation, decoder_implementation = video_codec_params diff --git a/uv.lock b/uv.lock index 4df93c3..ffb858e 100644 --- a/uv.lock +++ b/uv.lock @@ -257,6 +257,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] +[[package]] +name = "pyjwt" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 }, +] + [[package]] name = "pyproject-hooks" version = "1.2.0" @@ -347,6 +356,7 @@ dev = [ { name = "mypy" }, { name = "nanobind" }, { name = "numpy" }, + { name = "pyjwt" }, { name = "pytest" }, { name = "python-dotenv" }, { name = "ruff" }, @@ -364,6 +374,7 @@ dev = [ { name = "mypy" }, { name = "nanobind", specifier = "~=2.2.0" }, { name = "numpy", specifier = ">=2.1" }, + { name = "pyjwt", specifier = ">=2.9.0" }, { name = "pytest" }, { name = "python-dotenv", specifier = ">=1.0" }, { name = "ruff" }, From 946d81b94ccbed4d50d3511a522a6258137620b6 Mon Sep 17 00:00:00 2001 From: voluntas Date: Sun, 10 Nov 2024 19:22:06 +0900 Subject: [PATCH 02/11] =?UTF-8?q?authz=20=E3=81=AE=E6=89=95=E3=81=84?= =?UTF-8?q?=E5=87=BA=E3=81=97=E3=81=8C=20audio/video=20=E3=81=A7=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E3=81=AB=E5=8B=95=E4=BD=9C=E3=81=97=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E8=A6=8B=E3=81=88?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_authz.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/test_authz.py b/tests/test_authz.py index ef55c76..82c0067 100644 --- a/tests/test_authz.py +++ b/tests/test_authz.py @@ -7,17 +7,18 @@ from client import SoraClient, SoraRole +@pytest.mark.skipif(reason="TODO: バグ") @pytest.mark.parametrize( "video_codec_params", [ # video_codec, encoder_implementation, decoder_implementation - ("VP8", "libvpx", "libvpx"), - ("VP9", "libvpx", "libvpx"), - ("AV1", "libaom", "dav1d"), + ("VP8", "libvpx"), + ("VP9", "libvpx"), + ("AV1", "libaom"), ], ) def test_sendonly_authz_video_codec_type(setup, video_codec_params): - video_codec, encoder_implementation, decoder_implementation = video_codec_params + video_codec_type, encoder_implementation = video_codec_params signaling_urls = setup.get("signaling_urls") channel_id_prefix = setup.get("channel_id_prefix") @@ -28,8 +29,9 @@ def test_sendonly_authz_video_codec_type(setup, video_codec_params): access_token = jwt.encode( { "channel_id": channel_id, + "audio": False, "video": True, - "video_codec_type": video_codec, + "video_codec_type": video_codec_type, }, secret, algorithm="HS256", @@ -40,8 +42,10 @@ def test_sendonly_authz_video_codec_type(setup, video_codec_params): signaling_urls, SoraRole.SENDONLY, channel_id, - audio=False, - video=True, + # audio True だけど、authz で audio False になる + audio=True, + # video False だけど、authz で video True になる + video=False, metadata=metadata, ) sendonly.connect(fake_video=True) @@ -49,15 +53,17 @@ def test_sendonly_authz_video_codec_type(setup, video_codec_params): time.sleep(5) assert sendonly.offer_message is not None - print(sendonly.offer_message) + assert sendonly.offer_message["sdp"] is not None + assert video_codec_type in sendonly.offer_message["sdp"] sendonly_stats = sendonly.get_stats() sendonly.disconnect() # codec が無かったら StopIteration 例外が上がる + # 統計で video が見つからないので謎挙動になってる sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec") - assert sendonly_codec_stats["mimeType"] == f"video/{video_codec}" + assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}" # outbound-rtp が無かったら StopIteration 例外が上がる outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp") From d94f9742343b10a767dc5592b25e2cf454a52277 Mon Sep 17 00:00:00 2001 From: voluntas Date: Sun, 10 Nov 2024 20:14:37 +0900 Subject: [PATCH 03/11] =?UTF-8?q?codec=5Ftype=20=E3=81=AF=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E9=80=9A=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_authz.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/test_authz.py b/tests/test_authz.py index 82c0067..f58658d 100644 --- a/tests/test_authz.py +++ b/tests/test_authz.py @@ -7,7 +7,6 @@ from client import SoraClient, SoraRole -@pytest.mark.skipif(reason="TODO: バグ") @pytest.mark.parametrize( "video_codec_params", [ @@ -29,7 +28,6 @@ def test_sendonly_authz_video_codec_type(setup, video_codec_params): access_token = jwt.encode( { "channel_id": channel_id, - "audio": False, "video": True, "video_codec_type": video_codec_type, }, @@ -42,10 +40,8 @@ def test_sendonly_authz_video_codec_type(setup, video_codec_params): signaling_urls, SoraRole.SENDONLY, channel_id, - # audio True だけど、authz で audio False になる - audio=True, - # video False だけど、authz で video True になる - video=False, + audio=False, + video=True, metadata=metadata, ) sendonly.connect(fake_video=True) From 5c8084f3074845ceb95d6ae04c4716cb26d67065 Mon Sep 17 00:00:00 2001 From: voluntas Date: Sun, 10 Nov 2024 21:22:44 +0900 Subject: [PATCH 04/11] =?UTF-8?q?audio/video=20true/false=20=E3=81=AE?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_authz.py | 58 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/test_authz.py b/tests/test_authz.py index f58658d..8c1deaf 100644 --- a/tests/test_authz.py +++ b/tests/test_authz.py @@ -7,6 +7,62 @@ from client import SoraClient, SoraRole +@pytest.mark.skipif(reason="Sora C++ SDK 側の確認が必要") +def test_sendonly_authz_video_true(setup): + """ + 認証成功時の払い出しで audio: False / video: True を払い出す + """ + signaling_urls = setup.get("signaling_urls") + channel_id_prefix = setup.get("channel_id_prefix") + secret = setup.get("secret") + + channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}" + + access_token = jwt.encode( + { + "channel_id": channel_id, + "audio": False, + "video": True, + # 現在時刻 + 300 秒 (5分) + "exp": int(time.time()) + 300, + }, + secret, + algorithm="HS256", + ) + metadata = {"access_token": access_token} + + sendonly = SoraClient( + signaling_urls, + SoraRole.SENDONLY, + channel_id, + audio=True, + video=False, + metadata=metadata, + ) + sendonly.connect(fake_video=False, fake_audio=True) + + time.sleep(5) + + assert sendonly.offer_message is not None + assert sendonly.offer_message["sdp"] is not None + assert "VP9" in sendonly.offer_message["sdp"] + + sendonly_stats = sendonly.get_stats() + + sendonly.disconnect() + + # codec が無かったら StopIteration 例外が上がる + # 統計で video が見つからないので謎挙動になってる + sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec") + assert sendonly_codec_stats["mimeType"] == "video/VP9" + + # outbound-rtp が無かったら StopIteration 例外が上がる + outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp") + assert outbound_rtp_stats["encoderImplementation"] == "libvpx" + assert outbound_rtp_stats["bytesSent"] > 0 + assert outbound_rtp_stats["packetsSent"] > 0 + + @pytest.mark.parametrize( "video_codec_params", [ @@ -30,6 +86,8 @@ def test_sendonly_authz_video_codec_type(setup, video_codec_params): "channel_id": channel_id, "video": True, "video_codec_type": video_codec_type, + # 現在時刻 + 300 秒 (5分) + "exp": int(time.time()) + 300, }, secret, algorithm="HS256", From bea2c1b06aa87bf377cb1f0dbe3f5c55204e3095 Mon Sep 17 00:00:00 2001 From: voluntas Date: Sun, 10 Nov 2024 21:29:29 +0900 Subject: [PATCH 05/11] =?UTF-8?q?lifetime=20expired=20=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_sora_disconnect.py | 79 +++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/tests/test_sora_disconnect.py b/tests/test_sora_disconnect.py index 84db707..e209c85 100644 --- a/tests/test_sora_disconnect.py +++ b/tests/test_sora_disconnect.py @@ -2,6 +2,7 @@ import time import uuid +import jwt import pytest from api import disconnect_connection_api from client import SoraClient, SoraRole @@ -9,6 +10,44 @@ from sora_sdk import SoraSignalingErrorCode +def test_websocket_signaling_only_lifetime_expired(setup): + signaling_urls = setup.get("signaling_urls") + channel_id_prefix = setup.get("channel_id_prefix") + secret = setup.get("secret") + + channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}" + + access_token = jwt.encode( + { + "channel_id": channel_id, + "audio": False, + "video": True, + "connection_lifetime": 3, + # 現在時刻 + 300 秒 (5分) + "exp": int(time.time()) + 300, + }, + secret, + algorithm="HS256", + ) + metadata = {"access_token": access_token} + + with SoraClient( + signaling_urls, + SoraRole.RECVONLY, + channel_id, + audio=True, + video=True, + metadata=metadata, + data_channel_signaling=False, + ignore_disconnect_websocket=False, + ) as conn: + time.sleep(5) + + assert conn.ws_close is True + assert conn.ws_close_code == 1000 + assert conn.ws_close_reason == "LIFETIME-EXPIRED" + + @pytest.mark.skipif(sys.platform != "linux", reason="linux でのみ実行する") def test_websocket_signaling_only_disconnect_api(setup): signaling_urls = setup.get("signaling_urls") @@ -46,8 +85,6 @@ def test_websocket_signaling_only_disconnect_api(setup): # assert conn.disconnect_reason is not None # assert "DISCONNECTED-API" in conn.disconnect_reason - # TODO: LIFETIME-EXPIRED のテスト - @pytest.mark.skipif(sys.platform != "linux", reason="linux でのみ実行する") def test_websocket_datachannel_signaling_disconnect_api(setup): @@ -86,7 +123,43 @@ def test_websocket_datachannel_signaling_disconnect_api(setup): # assert conn.disconnect_reason is not None # assert "DISCONNECTED-API" in conn.disconnect_reason - # TODO: LIFETIME-EXPIRED のテスト + +def test_websocket_datachannel_signaling_lifetime_expired(setup): + signaling_urls = setup.get("signaling_urls") + channel_id_prefix = setup.get("channel_id_prefix") + secret = setup.get("secret") + + channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}" + + access_token = jwt.encode( + { + "channel_id": channel_id, + "audio": True, + "video": True, + "connection_lifetime": 3, + # 現在時刻 + 300 秒 (5分) + "exp": int(time.time()) + 300, + }, + secret, + algorithm="HS256", + ) + metadata = {"access_token": access_token} + + with SoraClient( + signaling_urls, + SoraRole.RECVONLY, + channel_id, + audio=True, + video=True, + metadata=metadata, + data_channel_signaling=True, + ignore_disconnect_websocket=False, + ) as conn: + time.sleep(5) + + assert conn.ws_close is True + assert conn.ws_close_code == 1000 + assert conn.ws_close_reason == "LIFETIME-EXPIRED" @pytest.mark.skipif(sys.platform != "linux", reason="linux でのみ実行する") From 8e4b7774701b70fb37108e26153cd4709d0ae29c Mon Sep 17 00:00:00 2001 From: voluntas Date: Sun, 10 Nov 2024 21:30:48 +0900 Subject: [PATCH 06/11] =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_authz.py | 6 ++---- tests/test_sora_disconnect.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/test_authz.py b/tests/test_authz.py index 8c1deaf..0119325 100644 --- a/tests/test_authz.py +++ b/tests/test_authz.py @@ -29,7 +29,6 @@ def test_sendonly_authz_video_true(setup): secret, algorithm="HS256", ) - metadata = {"access_token": access_token} sendonly = SoraClient( signaling_urls, @@ -37,7 +36,7 @@ def test_sendonly_authz_video_true(setup): channel_id, audio=True, video=False, - metadata=metadata, + metadata={"access_token": access_token}, ) sendonly.connect(fake_video=False, fake_audio=True) @@ -92,7 +91,6 @@ def test_sendonly_authz_video_codec_type(setup, video_codec_params): secret, algorithm="HS256", ) - metadata = {"access_token": access_token} sendonly = SoraClient( signaling_urls, @@ -100,7 +98,7 @@ def test_sendonly_authz_video_codec_type(setup, video_codec_params): channel_id, audio=False, video=True, - metadata=metadata, + metadata={"access_token": access_token}, ) sendonly.connect(fake_video=True) diff --git a/tests/test_sora_disconnect.py b/tests/test_sora_disconnect.py index e209c85..80faa18 100644 --- a/tests/test_sora_disconnect.py +++ b/tests/test_sora_disconnect.py @@ -29,7 +29,6 @@ def test_websocket_signaling_only_lifetime_expired(setup): secret, algorithm="HS256", ) - metadata = {"access_token": access_token} with SoraClient( signaling_urls, @@ -37,7 +36,7 @@ def test_websocket_signaling_only_lifetime_expired(setup): channel_id, audio=True, video=True, - metadata=metadata, + metadata={"access_token": access_token}, data_channel_signaling=False, ignore_disconnect_websocket=False, ) as conn: @@ -143,7 +142,6 @@ def test_websocket_datachannel_signaling_lifetime_expired(setup): secret, algorithm="HS256", ) - metadata = {"access_token": access_token} with SoraClient( signaling_urls, @@ -151,7 +149,7 @@ def test_websocket_datachannel_signaling_lifetime_expired(setup): channel_id, audio=True, video=True, - metadata=metadata, + metadata={"access_token": access_token}, data_channel_signaling=True, ignore_disconnect_websocket=False, ) as conn: From 2efaba8aad1df5e16fec38c24732771aebc8336b Mon Sep 17 00:00:00 2001 From: voluntas Date: Sun, 10 Nov 2024 21:54:27 +0900 Subject: [PATCH 07/11] =?UTF-8?q?=E9=A0=86=E7=95=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_sora_disconnect.py | 116 +++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 38 deletions(-) diff --git a/tests/test_sora_disconnect.py b/tests/test_sora_disconnect.py index 80faa18..62ea5d8 100644 --- a/tests/test_sora_disconnect.py +++ b/tests/test_sora_disconnect.py @@ -10,43 +10,6 @@ from sora_sdk import SoraSignalingErrorCode -def test_websocket_signaling_only_lifetime_expired(setup): - signaling_urls = setup.get("signaling_urls") - channel_id_prefix = setup.get("channel_id_prefix") - secret = setup.get("secret") - - channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}" - - access_token = jwt.encode( - { - "channel_id": channel_id, - "audio": False, - "video": True, - "connection_lifetime": 3, - # 現在時刻 + 300 秒 (5分) - "exp": int(time.time()) + 300, - }, - secret, - algorithm="HS256", - ) - - with SoraClient( - signaling_urls, - SoraRole.RECVONLY, - channel_id, - audio=True, - video=True, - metadata={"access_token": access_token}, - data_channel_signaling=False, - ignore_disconnect_websocket=False, - ) as conn: - time.sleep(5) - - assert conn.ws_close is True - assert conn.ws_close_code == 1000 - assert conn.ws_close_reason == "LIFETIME-EXPIRED" - - @pytest.mark.skipif(sys.platform != "linux", reason="linux でのみ実行する") def test_websocket_signaling_only_disconnect_api(setup): signaling_urls = setup.get("signaling_urls") @@ -85,6 +48,43 @@ def test_websocket_signaling_only_disconnect_api(setup): # assert "DISCONNECTED-API" in conn.disconnect_reason +def test_websocket_signaling_only_lifetime_expired(setup): + signaling_urls = setup.get("signaling_urls") + channel_id_prefix = setup.get("channel_id_prefix") + secret = setup.get("secret") + + channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}" + + access_token = jwt.encode( + { + "channel_id": channel_id, + "audio": False, + "video": True, + "connection_lifetime": 3, + # 現在時刻 + 300 秒 (5分) + "exp": int(time.time()) + 300, + }, + secret, + algorithm="HS256", + ) + + with SoraClient( + signaling_urls, + SoraRole.RECVONLY, + channel_id, + audio=True, + video=True, + metadata={"access_token": access_token}, + data_channel_signaling=False, + ignore_disconnect_websocket=False, + ) as conn: + time.sleep(5) + + assert conn.ws_close is True + assert conn.ws_close_code == 1000 + assert conn.ws_close_reason == "LIFETIME-EXPIRED" + + @pytest.mark.skipif(sys.platform != "linux", reason="linux でのみ実行する") def test_websocket_datachannel_signaling_disconnect_api(setup): signaling_urls = setup.get("signaling_urls") @@ -203,4 +203,44 @@ def test_datachannel_only_signaling_disconnect_api(setup): assert conn.disconnect_reason is not None assert "DISCONNECTED-API" in conn.disconnect_reason - # TODO: LIFETIME-EXPIRED のテスト + +def test_datachannel_only_signaling_lifetime_expired(setup): + signaling_urls = setup.get("signaling_urls") + channel_id_prefix = setup.get("channel_id_prefix") + secret = setup.get("secret") + + channel_id = f"{channel_id_prefix}_{__name__}_{sys._getframe().f_code.co_name}_{uuid.uuid4()}" + + access_token = jwt.encode( + { + "channel_id": channel_id, + "audio": True, + "video": True, + "connection_lifetime": 3, + # 現在時刻 + 300 秒 (5分) + "exp": int(time.time()) + 300, + }, + secret, + algorithm="HS256", + ) + + with SoraClient( + signaling_urls, + SoraRole.RECVONLY, + channel_id, + audio=True, + video=True, + metadata={"access_token": access_token}, + data_channel_signaling=True, + ignore_disconnect_websocket=True, + ) as conn: + time.sleep(5) + + assert conn.close_message is not None + assert conn.close_message["type"] == "close" + assert conn.close_message["code"] == 1000 + assert conn.close_message["reason"] == "LIFETIME-EXPIRED" + + assert conn.disconnect_code == SoraSignalingErrorCode.CLOSE_SUCCEEDED + assert conn.disconnect_reason is not None + assert "LIFETIME-EXPIRED" in conn.disconnect_reason From c0b637f13a84379bc34a804334295fe21f40827a Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 8 Jan 2025 14:36:19 +0900 Subject: [PATCH 08/11] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=AB?= =?UTF-8?q?=E4=BD=BF=E3=81=86=E3=83=A9=E3=82=A4=E3=83=96=E3=83=A9=E3=83=AA?= =?UTF-8?q?=E3=81=AF=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E3=81=97=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- uv.lock | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1fc2256..c688674 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,12 +63,12 @@ dev-dependencies = [ "wheel~=0.45.1", "typing-extensions", "python-dotenv", - "numpy>=2.2.1", + "numpy", "httpx", "pytest", "ruff", "mypy", - "pyjwt>=2.9.0", + "pyjwt", ] [tool.ruff] diff --git a/uv.lock b/uv.lock index 42360ee..3ddb234 100644 --- a/uv.lock +++ b/uv.lock @@ -264,11 +264,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.9.0" +version = "2.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 }, + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, ] [[package]] @@ -378,8 +378,8 @@ dev = [ { name = "httpx" }, { name = "mypy" }, { name = "nanobind", specifier = "~=2.4.0" }, - { name = "numpy", specifier = ">=2.2.1" }, - { name = "pyjwt", specifier = ">=2.9.0" }, + { name = "numpy" }, + { name = "pyjwt" }, { name = "pytest" }, { name = "python-dotenv" }, { name = "ruff" }, From a059872102d40c336067dd1d72fe8f223ee658ab Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 8 Jan 2025 14:36:52 +0900 Subject: [PATCH 09/11] =?UTF-8?q?=E5=A4=89=E6=9B=B4=E5=B1=A5=E6=AD=B4?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5c2967b..929361b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -64,6 +64,8 @@ ### misc +- [ADD] pyjwt を dev-dependencies に追加する + - @voluntas - [ADD] macos-15 を E2E テストに追加する - @voluntas - [ADD] canary.py を追加 From 162df948aeccd8101035d20a948aa006d3cf535e Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 8 Jan 2025 14:40:56 +0900 Subject: [PATCH 10/11] =?UTF-8?q?boost=20=E3=81=AE=20URL=20=E3=82=92?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 24 +++++++++++++----------- buildbase.py | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 929361b..b82c46a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -64,15 +64,7 @@ ### misc -- [ADD] pyjwt を dev-dependencies に追加する - - @voluntas -- [ADD] macos-15 を E2E テストに追加する - - @voluntas -- [ADD] canary.py を追加 - - @voluntas -- [ADD] Python 3.13 を E2E テストに追加する - - @voluntas -- [ADD] macos-15 を E2E テストに追加する +- [UPDATE] Boost のダウンロード URL を変更する - @voluntas - [UPDATE] ubuntu-latest を ubuntu-24.04 に変更する - @voluntas @@ -92,15 +84,25 @@ - @voluntas - [CHANGE] サンプルアプリの E2E テストを一旦削除する - @voluntas +- [ADD] pyjwt を dev-dependencies に追加する + - @voluntas +- [ADD] macos-15 を E2E テストに追加する + - @voluntas +- [ADD] canary.py を追加 + - @voluntas +- [ADD] Python 3.13 を E2E テストに追加する + - @voluntas +- [ADD] macos-15 を E2E テストに追加する + - @voluntas - [ADD] tests/ に E2E テストを追加する - @voluntas +- [ADD] examples に E2E テストを追加する + - @voluntas - [FIX] run.py で local_sora_cpp_sdk_dir を設定した際に boost が引けなくなってしまっている問題を修正する - @tnoho - [FIX] examples の設定に virtual = true を指定するようにする - これを指定しないとエラーになる - @voluntas -- [ADD] examples に E2E テストを追加する - - @voluntas - [FIX] サイマルキャストの E2E テストについて encoderImplementation の値チェック内容を緩和する - サイマルキャストの encoderImplementation のチェックを文字列一致としていたが、帯域推定機能を有効にした後、値が安定しなくなったためチェック内容を緩和した - サイマルキャストの encoderImplementation の結果を以下の通り修正 diff --git a/buildbase.py b/buildbase.py index 99c9e7d..d2cf719 100644 --- a/buildbase.py +++ b/buildbase.py @@ -691,7 +691,7 @@ def build_and_install_boost( ): version_underscore = version.replace(".", "_") archive = download( - f"https://boostorg.jfrog.io/artifactory/main/release/{version}/source/boost_{version_underscore}.tar.gz", + f"https://archives.boost.io/release/{version}/source/boost_{version_underscore}.tar.gz", source_dir, ) extract(archive, output_dir=build_dir, output_dirname="boost") From 52aec4554842e1daa223b3964f9fe3e09d4683ac Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 8 Jan 2025 14:44:39 +0900 Subject: [PATCH 11/11] =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_authz.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_authz.py b/tests/test_authz.py index 0119325..63edd7e 100644 --- a/tests/test_authz.py +++ b/tests/test_authz.py @@ -7,10 +7,11 @@ from client import SoraClient, SoraRole -@pytest.mark.skipif(reason="Sora C++ SDK 側の確認が必要") +@pytest.mark.skipif(reason="Sora C++ SDK 側の対応が必要") def test_sendonly_authz_video_true(setup): """ - 認証成功時の払い出しで audio: False / video: True を払い出す + - type: connect で audio: true / video: false で繫ぐ + - 認証成功時の払い出しで audio: false / video: true を払い出す """ signaling_urls = setup.get("signaling_urls") channel_id_prefix = setup.get("channel_id_prefix")