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
3 changes: 3 additions & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
History
=======
4.0.6
* bugfix: default GMCP modules requested are now in lowercase instead of titlecase

4.0.5
* enhancement: ``telnetlib3-client`` client shell now drains stdout.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "telnetlib3"
version = "4.0.5" # Keep in sync with telnetlib3/accessories.py::get_version !
version = "4.0.6" # Keep in sync with telnetlib3/accessories.py::get_version !
description = " Python Telnet server and client CLI and Protocol library"
readme = "README.rst"
license = "ISC"
Expand Down
2 changes: 1 addition & 1 deletion telnetlib3/accessories.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

def get_version() -> str:
"""Return the current version of telnetlib3."""
return "4.0.5" # keep in sync with pyproject.toml !
return "4.0.6" # keep in sync with pyproject.toml !


def encoding_from_lang(lang: str) -> Optional[str]:
Expand Down
21 changes: 11 additions & 10 deletions telnetlib3/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
#: Sub-modules are listed explicitly because not all servers treat
#: top-level subscriptions as wildcards.
_DEFAULT_GMCP_MODULES = [
"Char 1",
"Char.Vitals 1",
"Char.Items 1",
"Room 1",
"Room.Info 1",
"Comm 1",
"Comm.Channel 1",
"Group 1",
"char 1",
"char.vitals 1",
"char.items 1",
"room 1",
"room.info 1",
"comm 1",
"comm.channel 1",
"group 1",
]


Expand Down Expand Up @@ -197,8 +197,9 @@ def _send_gmcp_hello(self) -> None:
from telnetlib3.accessories import get_version

self.writer.send_gmcp("Core.Hello", {"client": "telnetlib3", "version": get_version()})
self.writer.send_gmcp("Core.Supports.Set", self._gmcp_modules)
self.log.info("GMCP handshake: Core.Hello + Core.Supports.Set %s", self._gmcp_modules)
wire_modules = [m.lower() for m in self._gmcp_modules]
self.writer.send_gmcp("Core.Supports.Set", wire_modules)
self.log.info("GMCP handshake: Core.Hello + Core.Supports.Set %s", wire_modules)

def on_gmcp(self, package: str, data: Any) -> None:
"""Store incoming GMCP data on ``writer.ctx``, merging dict updates."""
Expand Down
47 changes: 47 additions & 0 deletions telnetlib3/tests/test_client_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys
import types
import asyncio
from unittest import mock

# 3rd party
import pytest
Expand Down Expand Up @@ -516,6 +517,52 @@ async def test_on_gmcp_merges_dicts_on_writer_ctx():
assert client.writer.ctx.gmcp_data["Char.Vitals"] == {"hp": 63, "maxhp": 100}


def test_default_gmcp_modules_are_lowercase():
for spec in cl._DEFAULT_GMCP_MODULES:
module_part = spec.rsplit(" ", 1)[0]
assert module_part == module_part.lower()


@pytest.mark.asyncio
async def test_send_gmcp_hello_lowercases_default_modules():
client = _make_client()
calls: list[tuple[str, object]] = []
mock_writer = mock.Mock()
mock_writer.send_gmcp.side_effect = lambda pkg, data: calls.append((pkg, data))
client.writer = mock_writer
client._send_gmcp_hello()
assert client._gmcp_hello_sent is True
assert len(calls) == 2
pkg_name, supports_set = calls[1]
assert pkg_name == "Core.Supports.Set"
for spec in supports_set:
assert spec == spec.lower()


@pytest.mark.asyncio
async def test_send_gmcp_hello_lowercases_consumer_modules():
client = _make_client(gmcp_modules=["Room.Info 1", "Char 1"])
calls: list[tuple[str, object]] = []
mock_writer = mock.Mock()
mock_writer.send_gmcp.side_effect = lambda pkg, data: calls.append((pkg, data))
client.writer = mock_writer
client._send_gmcp_hello()
_, supports_set = calls[1]
assert "room.info 1" in supports_set
assert "char 1" in supports_set


@pytest.mark.asyncio
async def test_send_gmcp_hello_idempotent():
client = _make_client()
mock_writer = mock.Mock()
client.writer = mock_writer
client._send_gmcp_hello()
call_count = mock_writer.send_gmcp.call_count
client._send_gmcp_hello()
assert mock_writer.send_gmcp.call_count == call_count


def test_fingerprint_main_oserror(monkeypatch):
async def _bad_fp():
raise OSError("connection refused")
Expand Down
Loading