moo.sdk.tests.test_output_oob

Tests for the OOB-related SDK output functions:

  • send_oob raw IAC subnegotiation publish

  • send_gmcp GMCP frame publish (gated on negotiated capability)

  • play_sound GMCP Client.Media.Play / MSP fallback

  • room_info_payload IRE-style Room.Info payload builder

  • _client_supports capability lookup with cache fallback

  • _client_supports_gmcp_package GMCP package map lookup

  • can_open_editor rich-mode / GMCP-Editor gating

  • get_session_setting / set_session_setting / get_wrap_column / boot_player permission and edge-case branches

Functions

test_boot_player_publishes_disconnect_event_for_wizard()

Wizards may boot anyone; a disconnect event is queued for the target.

test_boot_player_rejects_non_wizard_booting_someone_else()

A mortal can boot themselves (handled elsewhere) but not other players.

test_can_open_editor_false_in_raw_mode_without_gmcp_editor_package()

Raw-mode without the bridge falls back to the inline with "..." form — editor unavailable.

test_can_open_editor_true_in_raw_mode_when_client_advertises_editor_package()

Raw-mode clients with the GMCP Editor package can still open the editor (Mudlet bridge path).

test_can_open_editor_true_in_rich_mode()

Rich-mode (prompt_toolkit TUI) sessions can always show the editor.

test_client_supports_falls_back_to_django_cache_for_celery_workers()

In Celery the SSH server's _session_settings is empty; the cache mirror is the fallback.

test_client_supports_gmcp_package_falls_back_to_cache()

Cross-process: Celery workers read the cache-mirrored package map when in-process is empty.

test_client_supports_gmcp_package_reads_packages_dict()

The GMCP package map is keyed by package name; presence-only (we ignore the version int).

test_client_supports_gmcp_package_returns_false_for_none_obj()

Defensive guard for callers that may pass None (e.g. can_open_editor with no player).

test_client_supports_reads_in_process_session_settings()

The hot path is the in-process dict — no cache hit needed when the SSH server populated it.

test_client_supports_returns_false_when_no_player_avatar()

A non-player obj (Generic Thing, etc.) yields False without raising.

test_get_session_setting_returns_default_when_no_player_in_context()

Outside of a verb context (no context.player) the default is returned.

test_get_wrap_column_falls_back_to_80_for_unparseable_property()

A garbage wrap_column (non-numeric, non-auto) falls back to the 80-column default.

test_get_wrap_column_honors_explicit_integer_property()

A non-auto integer wrap_column overrides the terminal width.

test_get_wrap_column_returns_terminal_width_when_property_missing()

No wrap_column property → fall through to the terminal width session setting.

test_play_sound_falls_back_to_msp_marker_when_only_msp()

A client that negotiated only MSP gets the inline !!SOUND(...) marker, not a GMCP frame.

test_play_sound_no_negotiated_protocol_is_noop()

Without GMCP or MSP we publish nothing — vanilla SSH would render the marker as text.

test_play_sound_rejects_non_wizard_caller()

test_play_sound_uses_gmcp_when_negotiated()

When the client speaks GMCP we send Client.Media.Play rather than the inline marker.

test_room_info_payload_falls_back_to_raw_name_for_unknown_directions()

Custom non-cardinal exits round-trip unchanged so the client still renders them.

test_room_info_payload_handles_first_word_match_for_compound_names()

DjangoMOO names exits "east from grand foyer" — the first word is the direction.

test_room_info_payload_handles_room_with_no_exits()

A room with no exits property returns an empty exits dict, not a crash.

test_room_info_payload_maps_cardinal_directions_to_short_codes()

"north" becomes "n", "northeast""ne", etc.

test_room_info_payload_skips_exits_with_no_destination()

Exits that have no dest (broken / not yet linked) are silently dropped from the map.

test_room_info_payload_swallows_attribute_error_on_aliases()

Some exit objects lack an aliases manager; that branch must not crash payload generation.

test_room_info_payload_uses_alias_when_name_does_not_match()

Custom-named exits still get a short code via their alias list.

test_send_gmcp_bare_module_omits_payload()

Calling without data emits the module name only — used for Core.Ping style events.

test_send_gmcp_publishes_oob_frame_when_negotiated()

A negotiated client receives an IAC SB GMCP <module> <json> IAC SE frame.

test_send_gmcp_rejects_non_wizard_caller()

test_send_gmcp_skips_when_client_did_not_negotiate()

If _client_supports("gmcp") is False, send_gmcp is a no-op (no publish).

test_send_oob_accepts_bytearray_and_normalizes_to_bytes()

A bytearray argument is normalized to bytes before publish so the queue serialiser is happy.

test_send_oob_publishes_event_with_bytes_payload()

send_oob() emits an {"event": "oob", "data": <bytes>} to _publish_to_player.

test_send_oob_rejects_non_bytes_payload()

String / int payloads are rejected before reaching the queue.

test_send_oob_rejects_non_wizard_caller()

Non-wizard callers cannot send raw OOB frames.

test_set_session_setting_noop_outside_player_context()

No player → no cache write, no publish — but no exception either.