Skip to content
Merged
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
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions CLAUDE.md
8 changes: 8 additions & 0 deletions fishjam/events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

# Exported messages
from fishjam.events._protos.fishjam import (
ServerMessageChannelAdded,
ServerMessageChannelRemoved,
ServerMessagePeerAdded,
ServerMessagePeerConnected,
ServerMessagePeerCrashed,
Expand All @@ -14,6 +16,8 @@
ServerMessageRoomDeleted,
ServerMessageStreamConnected,
ServerMessageStreamDisconnected,
ServerMessageStreamerConnected,
ServerMessageStreamerDisconnected,
ServerMessageTrackAdded,
ServerMessageTrackMetadataUpdated,
ServerMessageTrackRemoved,
Expand All @@ -34,6 +38,10 @@
"ServerMessagePeerCrashed",
"ServerMessageStreamConnected",
"ServerMessageStreamDisconnected",
"ServerMessageStreamerConnected",
"ServerMessageStreamerDisconnected",
"ServerMessageChannelAdded",
"ServerMessageChannelRemoved",
"ServerMessageTrackAdded",
"ServerMessageTrackMetadataUpdated",
"ServerMessageTrackRemoved",
Expand Down
18 changes: 12 additions & 6 deletions fishjam/events/allowed_notifications.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import Union

from fishjam.events import (
ServerMessageChannelAdded,
ServerMessageChannelRemoved,
ServerMessagePeerAdded,
ServerMessagePeerConnected,
ServerMessagePeerCrashed,
Expand All @@ -10,8 +12,8 @@
ServerMessageRoomCrashed,
ServerMessageRoomCreated,
ServerMessageRoomDeleted,
ServerMessageStreamConnected,
ServerMessageStreamDisconnected,
ServerMessageStreamerConnected,
ServerMessageStreamerDisconnected,
ServerMessageTrackAdded,
ServerMessageTrackMetadataUpdated,
ServerMessageTrackRemoved,
Expand All @@ -29,8 +31,10 @@
ServerMessagePeerDisconnected,
ServerMessagePeerMetadataUpdated,
ServerMessagePeerCrashed,
ServerMessageStreamConnected,
ServerMessageStreamDisconnected,
ServerMessageStreamerConnected,
ServerMessageStreamerDisconnected,
ServerMessageChannelAdded,
ServerMessageChannelRemoved,
ServerMessageViewerConnected,
ServerMessageViewerDisconnected,
ServerMessageTrackAdded,
Expand All @@ -48,8 +52,10 @@
ServerMessagePeerDisconnected,
ServerMessagePeerMetadataUpdated,
ServerMessagePeerCrashed,
ServerMessageStreamConnected,
ServerMessageStreamDisconnected,
ServerMessageStreamerConnected,
ServerMessageStreamerDisconnected,
ServerMessageChannelAdded,
ServerMessageChannelRemoved,
ServerMessageViewerConnected,
ServerMessageViewerDisconnected,
ServerMessageTrackAdded,
Expand Down
72 changes: 72 additions & 0 deletions tests/test_allowed_notifications.py
Original file line number Diff line number Diff line change
@@ -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}"
)
Loading