From 83a436dfbcee619fba0025e4984856eff9d604a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Wed, 22 Apr 2026 12:53:27 +0200 Subject: [PATCH] Update allowed notifications --- AGENTS.md | 13 +++++ CLAUDE.md | 1 + fishjam/events/__init__.py | 8 +++ fishjam/events/allowed_notifications.py | 18 ++++--- tests/test_allowed_notifications.py | 72 +++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 tests/test_allowed_notifications.py diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..cefb1c2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +# AGENTS.md + +## Development workflow + +### Red-Green TDD for new features + +Whenever a new feature is added, follow the red-green TDD cycle: + +1. **Red** — write a failing test that describes the desired behavior. Run it and confirm it fails for the expected reason. +2. **Green** — write the minimum production code needed to make the test pass. Run the test and confirm it passes. +3. **Refactor** — clean up the implementation and tests while keeping the suite green. + +Do not write production code for a new feature before a failing test exists for it. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/fishjam/events/__init__.py b/fishjam/events/__init__.py index f5e1feb..57c113c 100644 --- a/fishjam/events/__init__.py +++ b/fishjam/events/__init__.py @@ -2,6 +2,8 @@ # Exported messages from fishjam.events._protos.fishjam import ( + ServerMessageChannelAdded, + ServerMessageChannelRemoved, ServerMessagePeerAdded, ServerMessagePeerConnected, ServerMessagePeerCrashed, @@ -14,6 +16,8 @@ ServerMessageRoomDeleted, ServerMessageStreamConnected, ServerMessageStreamDisconnected, + ServerMessageStreamerConnected, + ServerMessageStreamerDisconnected, ServerMessageTrackAdded, ServerMessageTrackMetadataUpdated, ServerMessageTrackRemoved, @@ -34,6 +38,10 @@ "ServerMessagePeerCrashed", "ServerMessageStreamConnected", "ServerMessageStreamDisconnected", + "ServerMessageStreamerConnected", + "ServerMessageStreamerDisconnected", + "ServerMessageChannelAdded", + "ServerMessageChannelRemoved", "ServerMessageTrackAdded", "ServerMessageTrackMetadataUpdated", "ServerMessageTrackRemoved", diff --git a/fishjam/events/allowed_notifications.py b/fishjam/events/allowed_notifications.py index 5e775a3..6b4b7d3 100644 --- a/fishjam/events/allowed_notifications.py +++ b/fishjam/events/allowed_notifications.py @@ -1,6 +1,8 @@ from typing import Union from fishjam.events import ( + ServerMessageChannelAdded, + ServerMessageChannelRemoved, ServerMessagePeerAdded, ServerMessagePeerConnected, ServerMessagePeerCrashed, @@ -10,8 +12,8 @@ ServerMessageRoomCrashed, ServerMessageRoomCreated, ServerMessageRoomDeleted, - ServerMessageStreamConnected, - ServerMessageStreamDisconnected, + ServerMessageStreamerConnected, + ServerMessageStreamerDisconnected, ServerMessageTrackAdded, ServerMessageTrackMetadataUpdated, ServerMessageTrackRemoved, @@ -29,8 +31,10 @@ ServerMessagePeerDisconnected, ServerMessagePeerMetadataUpdated, ServerMessagePeerCrashed, - ServerMessageStreamConnected, - ServerMessageStreamDisconnected, + ServerMessageStreamerConnected, + ServerMessageStreamerDisconnected, + ServerMessageChannelAdded, + ServerMessageChannelRemoved, ServerMessageViewerConnected, ServerMessageViewerDisconnected, ServerMessageTrackAdded, @@ -48,8 +52,10 @@ ServerMessagePeerDisconnected, ServerMessagePeerMetadataUpdated, ServerMessagePeerCrashed, - ServerMessageStreamConnected, - ServerMessageStreamDisconnected, + ServerMessageStreamerConnected, + ServerMessageStreamerDisconnected, + ServerMessageChannelAdded, + ServerMessageChannelRemoved, ServerMessageViewerConnected, ServerMessageViewerDisconnected, ServerMessageTrackAdded, diff --git a/tests/test_allowed_notifications.py b/tests/test_allowed_notifications.py new file mode 100644 index 0000000..99da6b3 --- /dev/null +++ b/tests/test_allowed_notifications.py @@ -0,0 +1,72 @@ +import dataclasses + +from fishjam import events +from fishjam.events import _protos +from fishjam.events._protos.fishjam import ServerMessage +from fishjam.events.allowed_notifications import ALLOWED_NOTIFICATIONS + +# Types intentionally NOT published as SDK notifications. +# Adding a new oneof member to ServerMessage.content forces a maintainer +# decision: either add it to ALLOWED_NOTIFICATIONS (and re-export from +# fishjam.events), or add it here with a comment. +IGNORED_NOTIFICATIONS = { + # Auth / subscribe handshake — transport-level, not user-facing events. + "ServerMessageAuthenticated", + "ServerMessageAuthRequest", + "ServerMessageSubscribeRequest", + "ServerMessageSubscribeResponse", + # Not surfaced to SDK users - support for compositions is REST api only + # and not supported in SDKs + "ServerMessageTrackForwarding", + "ServerMessageTrackForwardingRemoved", + "ServerMessageVadNotification", + # Deprecated in the proto. + "ServerMessageStreamConnected", + "ServerMessageStreamDisconnected", + "ServerMessageHlsPlayable", + "ServerMessageHlsUploaded", + "ServerMessageHlsUploadCrashed", + "ServerMessageComponentCrashed", +} + + +def _oneof_content_types() -> list[type]: + types = [] + for field in dataclasses.fields(ServerMessage): + meta = field.metadata.get("betterproto") + if meta is not None and getattr(meta, "group", None) == "content": + types.append(getattr(_protos.fishjam, field.type)) + return types + + +def test_every_content_oneof_is_allowed_or_explicitly_ignored(): + allowed_names = {cls.__name__ for cls in ALLOWED_NOTIFICATIONS} + undecided = [ + cls.__name__ + for cls in _oneof_content_types() + if cls.__name__ not in allowed_names + and cls.__name__ not in IGNORED_NOTIFICATIONS + ] + assert not undecided, ( + "New ServerMessage.content oneof members found without a maintainer " + "decision. Add each to ALLOWED_NOTIFICATIONS (and re-export from " + "fishjam.events) or to IGNORED_NOTIFICATIONS in this test:\n - " + + "\n - ".join(sorted(undecided)) + ) + + +def test_allowed_and_ignored_are_disjoint(): + overlap = {cls.__name__ for cls in ALLOWED_NOTIFICATIONS} & IGNORED_NOTIFICATIONS + assert not overlap, f"Types cannot be both allowed and ignored: {overlap}" + + +def test_allowed_notifications_are_reexported_from_package(): + missing = [ + cls.__name__ + for cls in ALLOWED_NOTIFICATIONS + if cls.__name__ not in events.__all__ or not hasattr(events, cls.__name__) + ] + assert not missing, ( + "Every ALLOWED_NOTIFICATIONS type must be re-exported from " + f"fishjam.events. Missing: {missing}" + )