Skip to content

Add unit tests for osism/services websocket_manager and event_bridge#2460

Open
berendt wants to merge 1 commit into
mainfrom
unit-tests-services
Open

Add unit tests for osism/services websocket_manager and event_bridge#2460
berendt wants to merge 1 commit into
mainfrom
unit-tests-services

Conversation

@berendt

@berendt berendt commented Jul 13, 2026

Copy link
Copy Markdown
Member

Closes #2357.

Creates the tests/unit/services/ package with unit tests for osism/services/websocket_manager.py and osism/services/event_bridge.py, which previously had no coverage.

test_websocket_manager.py

  • EventMessage: constructor attributes, UUID4 id uniqueness, ISO 8601 Z-suffixed timestamp, exact to_dict() keys, to_json() round trip
  • WebSocketConnection.matches_filters(): no-filter pass-through, event/node/service filters incl. node_name=None rejection, ""unknown service mapping and AND-combination of filters
  • WebSocketManager.connect()/disconnect(): accept + registration, broadcaster started only once while running (done() check), restart after completion, no-op disconnect for unknown websockets
  • update_filters(): full and partial updates, clearing via [], unknown websocket no-op
  • add_event()/send_heartbeat(): queueing, early return without connections, heartbeat event shape
  • broadcast_event_from_notification(): identifier extraction for baremetal/compute/nova/network/neutron/volume/image/identity payloads incl. fallback keys, missing payload keys, unknown service types, empty event type, payload copy semantics and the swallowed-exception path
  • _broadcast_events(): filtered delivery, WebSocketDisconnect and generic-error connection cleanup, consumption without connections and clean cancellation

test_event_bridge.py

  • _init_redis(): default connection parameters, REDIS_HOST/REDIS_PORT/REDIS_DB overrides, ping failure handling and the REDIS_AVAILABLE = False local-queue-only path
  • set_websocket_manager(): thread startup guards for subscriber and processor threads
  • add_event(): publish payload, zero-subscriber warning, reconnect-then-publish, fallback to the local queue when reconnection fails, queue.Full and generic error swallowing
  • _redis_subscriber_loop(): missing subscriber, subscribe/get_message flow, message dispatch with and without a WebSocket manager, invalid JSON, processing errors, resubscribe after get_message failures, back-off with _init_redis() re-invocation when subscribe() raises, retry exhaustion and swallowed close() errors
  • _process_single_event(): missing-manager warning, AsyncMock broadcast dispatch and swallowed coroutine errors
  • _process_events(): queued event processing with task_done(), empty-queue continuation, pre-set shutdown exit and error continuation
  • shutdown(): shutdown event, subscriber close incl. error path and conditional thread joins

Notes

  • All tests use fresh WebSocketManager/EventBridge instances instead of the module-level singletons; EventBridge is always constructed with redis.Redis patched.
  • The thread loops are called synchronously and terminated via _shutdown_event-driven side effects; the asyncio broadcaster tests bound the task with cancellation in finally and carry pytest-timeout markers, so no test can hang CI.
  • pytest-asyncio 1.4.0 is added to the Pipfile [dev-packages] (compatible with pytest 9, pytest<10,>=8.4); async tests are marked explicitly with @pytest.mark.asyncio, so --strict-markers stays happy. Pipfile.lock was regenerated on linux/amd64 with Python 3.12 to match CI.
  • One deviation from the issue text: a failing get_message() only breaks the inner loop and triggers a resubscribe; the retry counter and _shutdown_event.wait() back-off only engage when subscribe() itself raises. The tests pin the actual behavior for both paths.

🤖 Generated with Claude Code

Create the tests/unit/services package with test modules for
osism/services/websocket_manager.py and osism/services/event_bridge.py,
which previously had no unit test coverage.

test_websocket_manager.py covers the EventMessage value object, the
per-connection filter logic in WebSocketConnection.matches_filters, the
WebSocketManager connection registry (connect/disconnect/update_filters),
the event queue helpers (add_event/send_heartbeat), the per-service
identifier extraction in broadcast_event_from_notification and the
_broadcast_events broadcaster loop including disconnect cleanup and
cancellation handling.

test_event_bridge.py covers Redis initialization including environment
variable overrides and connection failures, the publish path in add_event
with reconnect and local-queue fallback, thread startup guards in
set_websocket_manager, the _redis_subscriber_loop including resubscribe
and retry exhaustion, _process_single_event, the _process_events loop and
shutdown. The thread loops are called synchronously and terminated via
_shutdown_event-driven side effects, so no real threads, sockets or
sleeps are involved.

Most WebSocketManager methods are coroutines, so pytest-asyncio is added
to the Pipfile dev packages (with asyncio_default_fixture_loop_scope set
in setup.cfg); async tests are marked with pytest.mark.asyncio explicitly
to keep --strict-markers happy. Loop-driving tests carry pytest-timeout
markers so a regression cannot hang CI.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@osism.tech>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • Several tests reach deeply into EventBridge/WebSocketManager internals (e.g. _redis_client, _redis_subscriber, _shutdown_event, private thread fields), which may make future refactoring harder; consider adding small public helpers or using dependency injection to exercise behavior without coupling to private attributes.
  • The WebSocketManager broadcaster tests rely on fixed asyncio.sleep durations to assert behavior; using synchronization primitives (e.g. an event set after send_text is awaited or draining the queue) would make these tests more deterministic and less timing‑sensitive.
  • The event extraction parametrization in test_broadcast_event_from_notification is quite dense; extracting the payload-building logic into named helpers or smaller focused tests per service type could improve readability and ease future changes to the extraction rules.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Several tests reach deeply into EventBridge/WebSocketManager internals (e.g. _redis_client, _redis_subscriber, _shutdown_event, private thread fields), which may make future refactoring harder; consider adding small public helpers or using dependency injection to exercise behavior without coupling to private attributes.
- The WebSocketManager broadcaster tests rely on fixed asyncio.sleep durations to assert behavior; using synchronization primitives (e.g. an event set after send_text is awaited or draining the queue) would make these tests more deterministic and less timing‑sensitive.
- The event extraction parametrization in test_broadcast_event_from_notification is quite dense; extracting the payload-building logic into named helpers or smaller focused tests per service type could improve readability and ease future changes to the extraction rules.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

Unit tests for osism/services/{websocket_manager,event_bridge}.py

2 participants