moo.shell.tests.test_iac

IAC parser, encoder, and negotiator tests.

Covers the wire format for every protocol the shell actually speaks (GMCP, MSSP, MTTS/TTYPE, CHARSET, MSP, GA/EOR) plus the negotiator’s option-state machine.

Functions

test_encode_charset_accepted_utf8()

test_encode_charset_request_honors_custom_separator()

RFC 2066 lets the server pick any single-byte ASCII separator.

test_encode_charset_request_uses_default_space_separator()

test_encode_cmd_produces_iac_triple()

test_encode_gmcp_bare_module()

test_encode_gmcp_with_data()

test_encode_mssp_multi_value_repeats_val_tag()

test_encode_mssp_single_values()

test_encode_sb_doubles_iac_in_payload()

An 0xFF byte inside an SB payload must be doubled to (IAC, IAC).

test_encode_ttype_send()

test_is_known_mud_client_matches_major_clients()

test_is_known_mud_client_rejects_ordinary_terminals()

test_msp_music_marker_custom_volume()

test_msp_music_marker_default_volume()

test_msp_sound_marker_custom_volume_priority()

test_msp_sound_marker_defaults()

test_negotiator_charset_request_utf8_accepted()

test_negotiator_charset_request_with_empty_list_rejected()

A REQUEST with no separator/charsets is rejected to keep the wire predictable.

test_negotiator_charset_request_without_utf8_rejected()

test_negotiator_charset_sb_empty_payload_is_silent()

An empty CHARSET subnegotiation is dropped, not rejected.

test_negotiator_charset_sb_non_request_subcmd_is_silent()

ACCEPTED/REJECTED arriving at the server are no-ops (we initiated nothing).

test_negotiator_client_do_gmcp_sets_capability()

test_negotiator_client_do_unsupported_option_replies_wont()

test_negotiator_client_dont_disables_capability()

test_negotiator_client_mssp_request_invokes_provider()

test_negotiator_client_will_naws_replies_do_without_send()

NAWS is in _WE_ACCEPT_CLIENT but is not TTYPE — the reply is a plain DO with no follow-up subnegotiation request.

test_negotiator_client_will_ttype_requests_send()

test_negotiator_client_will_unsupported_replies_dont()

test_negotiator_client_wont_replies_dont()

test_negotiator_dont_we_offer_replies_wont()

Client DONT for an option we do offer must produce a WONT reply (telnet Q-method); for options we never offered the negotiator stays silent.

test_negotiator_finalize_ttype_without_callback_does_not_raise()

When no on_ttype is registered the finalize step still flips the capability flag.

test_negotiator_full_three_stage_mtts()

test_negotiator_gmcp_sb_invokes_callback()

test_negotiator_gmcp_sb_without_callback_is_silent()

If no on_gmcp was registered the negotiator drops the payload — no exception, no reply.

test_negotiator_initial_offers_include_expected_set()

test_negotiator_malformed_gmcp_does_not_raise()

Malformed GMCP payloads must not crash the session.

test_negotiator_mark_unknown_opt_does_not_pollute_capabilities()

DO/DONT for an option that has no entry in the label map (e.g. OPT_BINARY) must not write a stray capability key — capabilities only ever holds the documented set.

test_negotiator_mssp_sb_without_provider_is_silent()

Without an on_mssp_request provider the negotiator returns an empty reply.

test_negotiator_single_stage_ttype_client_finalizes()

A client that only offers one TTYPE value should still finalize after one loop.

test_negotiator_ttype_sb_in_stage_zero_is_ignored()

A TTYPE subnegotiation arriving before we ever asked for one (stage 0) is dropped — without this the state machine would advance into stage 2 on garbage input.

test_negotiator_ttype_sb_with_malformed_payload_is_silent()

A TTYPE SB whose first byte is not IS is dropped without raising.

test_negotiator_ttype_third_stage_loop_finalizes_without_mtts()

Some clients return three TTYPEs but the third stage echoes the second rather than an MTTS bitfield.

test_negotiator_ttype_third_stage_unexpected_value_is_recorded()

A non-MTTS, non-looped third TTYPE response is stashed in terminal_extra rather than crashing or being dropped silently.

test_negotiator_unknown_event_kind_returns_empty_bytes()

handle() is called from a generic dispatch loop; unknown kinds must not raise.

test_negotiator_unknown_sb_opt_returns_empty()

A subnegotiation for an option we don't handle (e.g. NAWS) is ignored.

test_parse_gmcp_bare_module()

test_parse_gmcp_module_with_trailing_space_returns_none_data()

A GMCP frame with the module name followed by a space but no JSON body decodes to (module, None) — the spec allows callers to send an empty payload as "<module> ", not just "<module>".

test_parse_gmcp_round_trip()

test_parse_mtts_bitfield_extracts_integer()

test_parse_ttype_is_extracts_name()

test_parse_ttype_is_rejects_wrong_tag()

test_parser_cmd_mixed_with_text()

test_parser_consumes_single_byte_iac_commands_silently()

NOP/DM/BRK/IP/AO/AYT/EC/EL after IAC are valid single-byte commands the server is allowed to ignore — but the parser must consume them, not leak them into the residual byte stream.

test_parser_eor_event()

test_parser_escaped_iac_in_text_is_literal_ff()

IAC IAC in a non-subneg context is a literal 0xFF in the user stream.

test_parser_ga_event()

test_parser_multiple_events_one_feed()

test_parser_partial_cmd_across_feeds()

test_parser_partial_subneg_across_feeds()

Split an IAC SB .

test_parser_plain_text_passes_through()

test_parser_recovers_from_corrupt_sb_frame()

A non-IAC/non-SE byte arriving after IAC inside an SB payload aborts the frame without raising; the parser is back in NORMAL state for the next feed and does not mix the abandoned payload into a later event.

test_parser_recovers_from_unexpected_byte_after_iac()

An IAC followed by an unexpected non-command byte is dropped, not raised.

test_parser_simple_cmd_will_opt()

test_parser_subneg_frame()

test_parser_subneg_with_escaped_iac_in_payload()

IAC IAC inside an SB payload is a literal 0xFF byte.

test_sb_round_trip_through_parser()