Merge pull request #62 from ProgVal/unreal

Add Unreal controller
This commit is contained in:
Val Lorentz
2021-07-03 09:54:22 +02:00
committed by GitHub
23 changed files with 519 additions and 48 deletions

View File

@ -183,6 +183,8 @@ class BaseServerController(_BaseController):
port: int
hostname: str
services_controller: BaseServicesController
extban_mute_char: Optional[str] = None
"""Character used for the 'mute' extban"""
def run(
self,

View File

@ -517,9 +517,15 @@ class BaseServerTestCase(
def getRegistrationMessage(self, client: TClientName) -> Message:
"""Filter notices, do not send pings."""
return self.getMessage(
client, synchronize=False, filter_pred=lambda m: m.command != "NOTICE"
)
while True:
msg = self.getMessage(
client, synchronize=False, filter_pred=lambda m: m.command != "NOTICE"
)
if msg.command == "PING":
# Hi Unreal
self.sendLine(client, "PONG :" + msg.params[0])
else:
return msg
def sendLine(self, client: TClientName, line: Union[str, bytes]) -> None:
return self.clients[client].sendLine(line)
@ -565,6 +571,9 @@ class BaseServerTestCase(
result.append(m)
if m.command == "001":
return result
elif m.command == "PING":
# Hi, Unreal
self.sendLine(client, "PONG :" + m.params[0])
def requestCapabilities(
self,

View File

@ -73,6 +73,7 @@ class CharybdisController(BaseServerController, DirectoryBasedController):
binary_name = "charybdis"
supported_sasl_mechanisms = {"PLAIN"}
supports_sts = False
extban_mute_char = None
def create_config(self) -> None:
super().create_config()

View File

@ -68,6 +68,7 @@ class InspircdController(BaseServerController, DirectoryBasedController):
software_name = "InspIRCd"
supported_sasl_mechanisms = {"PLAIN"}
supports_sts = False
extban_mute_char = "m"
def create_config(self) -> None:
super().create_config()

View File

@ -0,0 +1,182 @@
import os
import subprocess
from typing import Optional, Set, Type
from irctest.basecontrollers import (
BaseServerController,
DirectoryBasedController,
NotImplementedByController,
)
from irctest.irc_utils.junkdrawer import find_hostname_and_port
TEMPLATE_CONFIG = """
include "modules.default.conf";
me {{
name "My.Little.Server";
info "ExampleNET Server";
sid "001";
}}
admin {{
"Bob Smith";
"bob";
"email@example.org";
}}
class clients {{
pingfreq 90;
maxclients 1000;
sendq 200k;
recvq 8000;
}}
class servers {{
pingfreq 60;
connfreq 15; /* try to connect every 15 seconds */
maxclients 10; /* max servers */
sendq 20M;
}}
allow {{
mask *;
class clients;
maxperip 50;
{password_field}
}}
listen {{
ip {hostname};
port {port};
}}
listen {{
ip {tls_hostname};
port {tls_port};
options {{ tls; }}
tls-options {{
certificate "{pem_path}";
key "{key_path}";
}};
}}
/* Special SSL/TLS servers-only port for linking */
listen {{
ip {services_hostname};
port {services_port};
options {{ serversonly; }}
}}
link services.example.org {{
incoming {{
mask localhost;
}}
password "password";
class servers;
}}
ulines {{
services.example.org;
}}
set {{
kline-address "example@example.org";
network-name "ExampleNET";
default-server "irc.example.org";
help-channel "#Help";
cloak-keys {{ "aaaA1"; "bbbB2"; "cccC3"; }}
options {{
identd-check; // Disable it, so it doesn't prefix idents with a tilde
}}
anti-flood {{
// Prevent throttling, especially test_buffering.py which
// triggers anti-flood with its very long lines
unknown-users {{
lag-penalty 1;
lag-penalty-bytes 10000;
}}
}}
}}
tld {{
mask *;
motd "{empty_file}";
botmotd "{empty_file}";
rules "{empty_file}";
}}
"""
class UnrealircdController(BaseServerController, DirectoryBasedController):
software_name = "InspIRCd"
supported_sasl_mechanisms = {"PLAIN"}
supports_sts = False
extban_mute_char = "q"
def create_config(self) -> None:
super().create_config()
with self.open_file("server.conf"):
pass
def run(
self,
hostname: str,
port: int,
*,
password: Optional[str],
ssl: bool,
run_services: bool,
valid_metadata_keys: Optional[Set[str]] = None,
invalid_metadata_keys: Optional[Set[str]] = None,
restricted_metadata_keys: Optional[Set[str]] = None,
) -> None:
if valid_metadata_keys or invalid_metadata_keys:
raise NotImplementedByController(
"Defining valid and invalid METADATA keys."
)
assert self.proc is None
self.port = port
self.hostname = hostname
self.create_config()
(unused_hostname, unused_port) = find_hostname_and_port()
(services_hostname, services_port) = find_hostname_and_port()
password_field = 'password "{}";'.format(password) if password else ""
self.gen_ssl()
if ssl:
(tls_hostname, tls_port) = (hostname, port)
(hostname, port) = (unused_hostname, unused_port)
else:
# Unreal refuses to start without TLS enabled
(tls_hostname, tls_port) = (unused_hostname, unused_port)
with self.open_file("empty.txt") as fd:
fd.write("\n")
assert self.directory
with self.open_file("unrealircd.conf") as fd:
fd.write(
TEMPLATE_CONFIG.format(
hostname=hostname,
port=port,
services_hostname=services_hostname,
services_port=services_port,
tls_hostname=tls_hostname,
tls_port=tls_port,
password_field=password_field,
key_path=self.key_path,
pem_path=self.pem_path,
empty_file=os.path.join(self.directory, "empty.txt"),
)
)
self.proc = subprocess.Popen(
[
"unrealircd",
"-F", # BOOT_NOFORK
"-f",
os.path.join(self.directory, "unrealircd.conf"),
],
stdout=subprocess.DEVNULL,
)
if run_services:
raise NotImplementedByController("Registration services")
def get_irctest_controller_class() -> Type[UnrealircdController]:
return UnrealircdController

View File

@ -35,6 +35,14 @@ class StrRe(Operator):
return f"StrRe(r'{self.regexp}')"
@dataclasses.dataclass(frozen=True)
class NotStrRe(Operator):
regexp: str
def __repr__(self) -> str:
return f"NotStrRe(r'{self.regexp}')"
@dataclasses.dataclass(frozen=True)
class RemainingKeys(Operator):
"""Used in a dict pattern to match all remaining keys.
@ -54,6 +62,15 @@ ANYDICT = {RemainingKeys(ANYSTR): AnyOptStr()}
`match_dict(got_tags, {"label": "foo", **ANYDICT})`"""
class _AnyListRemainder:
def __repr__(self) -> str:
return "*ANYLIST"
ANYLIST = [_AnyListRemainder()]
"""Matches any list remainder"""
def match_string(got: Optional[str], expected: Union[str, Operator, None]) -> bool:
if isinstance(expected, AnyOptStr):
return True
@ -62,6 +79,9 @@ def match_string(got: Optional[str], expected: Union[str, Operator, None]) -> bo
elif isinstance(expected, StrRe):
if got is None or not re.match(expected.regexp, got):
return False
elif isinstance(expected, NotStrRe):
if got is None or re.match(expected.regexp, got):
return False
elif isinstance(expected, Operator):
raise NotImplementedError(f"Unsupported operator: {expected}")
elif got != expected:
@ -78,6 +98,9 @@ def match_list(
The ANYSTR operator can be used on the 'expected' side as a wildcard,
matching any *single* value; and StrRe("<regexp>") can be used to match regular
expressions"""
if expected[-1] is ANYLIST[0]:
expected = expected[0:-1]
got = got[0 : len(expected)] # Ignore remaining
if len(got) != len(expected):
return False
return all(

View File

@ -4,7 +4,7 @@ import pytest
from irctest import cases
from irctest.irc_utils.message_parser import parse_message
from irctest.patma import ANYDICT, ANYSTR, StrRe
from irctest.patma import ANYDICT, ANYSTR, AnyOptStr, NotStrRe, RemainingKeys, StrRe
# fmt: off
MESSAGE_SPECS: List[Tuple[Dict, List[str], List[str]]] = [
@ -131,6 +131,27 @@ MESSAGE_SPECS: List[Tuple[Dict, List[str], List[str]]] = [
":foo!baz@qux PRIVMSG #chan hello",
]
),
(
# the specification:
dict(
tags={"tag1": "bar", RemainingKeys(NotStrRe("tag2")): AnyOptStr()},
command="PRIVMSG",
params=["#chan", "hello"],
),
# matches:
[
"@tag1=bar PRIVMSG #chan :hello",
"@tag1=bar :foo!baz@qux PRIVMSG #chan :hello",
"@tag1=bar;tag3= PRIVMSG #chan :hello",
],
# and does not match:
[
"PRIVMG #chan :hello",
"@tag1=value1 PRIVMSG #chan :hello",
"@tag1=bar;tag2= PRIVMSG #chan :hello",
"@tag1=bar;tag2=baz PRIVMSG #chan :hello",
]
),
]
# fmt: on

View File

@ -33,11 +33,16 @@ class BotModeTestCase(cases.BaseServerTestCase):
self.sendLine("bot", f"MODE botnick +{self._mode_char}")
# Check echoed mode
self.assertMessageMatch(
self.getMessage("bot"),
command="MODE",
params=["botnick", StrRe(r"\+?" + self._mode_char)],
)
while True:
msg = self.getMessage("bot")
if msg.command != "NOTICE":
# Unreal sends the BOTMOTD here
self.assertMessageMatch(
msg,
command="MODE",
params=["botnick", StrRe(r"\+?" + self._mode_char)],
)
break
def testBotMode(self):
self._initBot()

View File

@ -125,6 +125,7 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
cap1 = "echo-message"
cap2 = "server-time"
self.addClient(1)
self.connectClient("sender")
self.sendLine(1, "CAP LS 302")
m = self.getRegistrationMessage(1)
if not ({cap1, cap2} <= set(m.params[2].split())):
@ -146,7 +147,10 @@ class CapTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
enabled_caps = set(cap_list.params[2].split())
enabled_caps.discard("cap-notify") # implicitly added by some impls
self.assertEqual(enabled_caps, {cap1, cap2})
self.assertIn("time", cap_list.tags, cap_list)
self.sendLine(2, "PRIVMSG bar :hi")
m = self.getMessage(1)
self.assertIn("time", m.tags, m)
# remove the server-time cap
self.sendLine(1, f"CAP REQ :-{cap2}")

View File

@ -26,7 +26,7 @@ from irctest.numerics import (
RPL_TOPIC,
RPL_TOPICTIME,
)
from irctest.patma import ANYSTR, StrRe
from irctest.patma import ANYLIST, ANYSTR, StrRe
MODERN_CAPS = [
"server-time",
@ -1296,16 +1296,13 @@ class OpModerated(cases.BaseServerTestCase):
class MuteExtban(cases.BaseServerTestCase):
"""https://defs.ircdocs.horse/defs/isupport.html#extban
These tests assume that if the server advertizes the 'm' extban,
then it supports mute.
It magically guesses what char the IRCd uses for mutes."""
This is not true of Charybdis, which introduced a conflicting 'm'
exban for matching hostmasks in 2015
(e2a9fa9cab3720215d8081e940109416e8214a29).
But Unreal was already using 'm' for muting since 2008
(f474e7e6dc2d36f96150ebe33b23b4ea76814415) and it is the most popular
definition so we're going with that one."""
def char(self):
if self.controller.extban_mute_char is None:
raise runner.ExtbanNotSupported("", "mute")
else:
return self.controller.extban_mute_char
@cases.mark_specifications("Ergo")
def testISupport(self):
@ -1313,7 +1310,7 @@ class MuteExtban(cases.BaseServerTestCase):
isupport = self.server_support
token = isupport["EXTBAN"]
prefix, comma, types = token.partition(",")
self.assertIn("m", types, "Missing 'm' in ISUPPORT EXTBAN")
self.assertIn(self.char, types, f"Missing '{self.char()}' in ISUPPORT EXTBAN")
self.assertEqual(prefix, "")
self.assertEqual(comma, ",")
@ -1325,15 +1322,15 @@ class MuteExtban(cases.BaseServerTestCase):
isupport = self.server_support
token = isupport.get("EXTBAN", "")
prefix, comma, types = token.partition(",")
if "m" not in types:
raise runner.ExtbanNotSupported("m", "mute")
if self.char() not in types:
raise runner.ExtbanNotSupported(self.char(), "mute")
clients = ("chanop", "bar")
# Mute "bar"
self.joinChannel("chanop", "#chan")
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan +b m:bar!*@*")
self.sendLine("chanop", f"MODE #chan +b {prefix}{self.char()}:bar!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1344,6 +1341,21 @@ class MuteExtban(cases.BaseServerTestCase):
for client in clients:
self.getMessages(client)
# "bar" sees the MODE too
self.sendLine("bar", "MODE #chan +b")
self.assertMessageMatch(
self.getMessage("bar"),
command="367",
params=[
"bar",
"#chan",
f"{prefix}{self.char()}:bar!*@*",
"chanop",
*ANYLIST,
],
)
self.getMessages("bar")
# "bar" talks: rejected
self.sendLine("bar", "PRIVMSG #chan :hi from bar")
replies = self.getMessages("bar")
@ -1354,7 +1366,7 @@ class MuteExtban(cases.BaseServerTestCase):
# remove mute on "bar" with -b
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan -b m:bar!*@*")
self.sendLine("chanop", f"MODE #chan -b {prefix}{self.char()}:bar!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1378,15 +1390,15 @@ class MuteExtban(cases.BaseServerTestCase):
isupport = self.server_support
token = isupport.get("EXTBAN", "")
prefix, comma, types = token.partition(",")
if "m" not in types:
raise runner.ExtbanNotSupported("m", "mute")
if self.char() not in types:
raise runner.ExtbanNotSupported(self.char(), "mute")
clients = ("chanop", "qux")
# Mute "qux"
self.joinChannel("chanop", "#chan")
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan +b m:qux!*@*")
self.sendLine("chanop", f"MODE #chan +b {prefix}{self.char()}:qux!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1437,17 +1449,17 @@ class MuteExtban(cases.BaseServerTestCase):
isupport = self.server_support
token = isupport.get("EXTBAN", "")
prefix, comma, types = token.partition(",")
if "m" not in types:
raise runner.ExtbanNotSupported("m", "mute")
if self.char() not in types:
raise runner.ExtbanNotSupported(self.char(), "mute")
if "e" not in self.server_support["CHANMODES"]:
raise runner.ChannelModeNotSupported("m", "mute")
raise runner.ChannelModeNotSupported(self.char(), "mute")
clients = ("chanop", "qux")
# Mute "qux"
self.joinChannel("chanop", "#chan")
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan +b m:qux!*@*")
self.sendLine("chanop", f"MODE #chan +b {prefix}{self.char()}:qux!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1472,11 +1484,13 @@ class MuteExtban(cases.BaseServerTestCase):
self.getMessages(client)
# +e grants an exemption to +b
self.sendLine("chanop", "MODE #chan +e m:*!~evan@*")
self.sendLine("chanop", f"MODE #chan +e {prefix}{self.char()}:*!~evan@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
self.getMessages("qux")
# so "qux" can now talk
self.sendLine("qux", "PRIVMSG #chan :thanks for mute-excepting me")
replies = self.getMessages("qux")
@ -1500,9 +1514,14 @@ class MuteExtban(cases.BaseServerTestCase):
clients = ("chanop", "bar")
self.connectClient("chanop", name="chanop")
isupport = self.server_support
token = isupport.get("EXTBAN", "")
prefix, comma, types = token.partition(",")
self.joinChannel("chanop", "#chan")
self.getMessages("chanop")
self.sendLine("chanop", "MODE #chan +b m:BAR!*@*")
self.sendLine("chanop", f"MODE #chan +b {prefix}{self.char()}:BAR!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)
@ -1521,7 +1540,7 @@ class MuteExtban(cases.BaseServerTestCase):
self.assertEqual(self.getMessages("chanop"), [])
# remove mute with -b
self.sendLine("chanop", "MODE #chan -b m:bar!*@*")
self.sendLine("chanop", f"MODE #chan -b {prefix}{self.char()}:bar!*@*")
replies = {msg.command for msg in self.getMessages("chanop")}
self.assertIn("MODE", replies)
self.assertNotIn(ERR_CHANOPRIVSNEEDED, replies)

View File

@ -119,15 +119,38 @@ class ConnectionRegistrationTestCase(cases.BaseServerTestCase):
self.sendLine(2, "NICK foo")
self.sendLine(1, "USER username * * :Realname")
self.sendLine(2, "USER username * * :Realname")
m1 = self.getRegistrationMessage(1)
m2 = self.getRegistrationMessage(2)
try:
m1 = self.getRegistrationMessage(1)
except (ConnectionClosed, ConnectionResetError):
# Unreal closes the connection, see
# https://bugs.unrealircd.org/view.php?id=5950
command1 = None
else:
command1 = m1.command
try:
m2 = self.getRegistrationMessage(2)
except (ConnectionClosed, ConnectionResetError):
# ditto
command2 = None
else:
command2 = m2.command
self.assertNotEqual(
(m1.command, m2.command),
(command1, command2),
("001", "001"),
"Two concurrently registering requesting the same nickname "
"both got 001.",
)
self.assertIn(
"001",
(command1, command2),
"Two concurrently registering requesting the same nickname "
"neither got 001.",
)
@cases.mark_specifications("IRCv3")
def testIrc301CapLs(self):
"""

View File

@ -2,6 +2,8 @@
<http://ircv3.net/specs/extensions/echo-message-3.2.html>
"""
import pytest
from irctest import cases
from irctest.basecontrollers import NotImplementedByController
from irctest.irc_utils.junkdrawer import random_name
@ -97,6 +99,7 @@ def _testEchoMessage(command, solo, server_time):
class EchoMessageTestCase(cases.BaseServerTestCase):
@pytest.mark.arbitrary_client_tags
@cases.mark_capabilities(
"batch", "labeled-response", "echo-message", "message-tags"
)

View File

@ -7,8 +7,10 @@ so there may be many false positives.
import re
import pytest
from irctest import cases
from irctest.patma import ANYDICT, StrRe
from irctest.patma import ANYDICT, AnyOptStr, NotStrRe, RemainingKeys, StrRe
class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
@ -89,6 +91,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
self.assertMessageMatch(m, command="PRIVMSG", tags={"label": "12345"})
@pytest.mark.react_tag
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
def testLabeledPrivmsgResponsesToChannel(self):
self.connectClient(
@ -190,6 +193,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
self.assertMessageMatch(m, command="NOTICE", tags={"label": "12345"})
@pytest.mark.react_tag
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
def testLabeledNoticeResponsesToChannel(self):
self.connectClient(
@ -265,6 +269,7 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
).format(number_of_labels),
)
@pytest.mark.react_tag
@cases.mark_capabilities(
"echo-message", "batch", "labeled-response", "message-tags"
)
@ -282,7 +287,14 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
)
self.getMessages(2)
self.sendLine(1, "@label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG bar")
# Need to get a valid msgid because Unreal validates them
self.sendLine(1, "PRIVMSG bar :hi")
msgid = self.getMessage(1).tags["msgid"]
assert msgid == self.getMessage(2).tags["msgid"]
self.sendLine(
1, f"@label=12345;+draft/reply={msgid};+draft/react=l😃l TAGMSG bar"
)
m = self.getMessage(1)
m2 = self.getMessage(2)
@ -290,7 +302,11 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
self.assertMessageMatch(
m2,
command="TAGMSG",
tags={"+draft/reply": "123", "+draft/react": "l😃l", **ANYDICT},
tags={
"+draft/reply": msgid,
"+draft/react": "l😃l",
RemainingKeys(NotStrRe("label")): AnyOptStr(),
},
)
self.assertNotIn(
"label",
@ -308,12 +324,13 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
command="TAGMSG",
tags={
"label": "12345",
"+draft/reply": "123",
"+draft/reply": msgid,
"+draft/react": "l😃l",
**ANYDICT,
},
)
@pytest.mark.react_tag
@cases.mark_capabilities(
"echo-message", "batch", "labeled-response", "message-tags"
)
@ -338,7 +355,14 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
self.getMessages(2)
self.getMessages(1)
self.sendLine(1, "@label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG #test")
# Need to get a valid msgid because Unreal validates them
self.sendLine(1, "PRIVMSG #test :hi")
msgid = self.getMessage(1).tags["msgid"]
assert msgid == self.getMessage(2).tags["msgid"]
self.sendLine(
1, f"@label=12345;+draft/reply={msgid};+draft/react=l😃l TAGMSG #test"
)
ms = self.getMessage(1)
mt = self.getMessage(2)
@ -346,6 +370,11 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
self.assertMessageMatch(
mt,
command="TAGMSG",
tags={
"+draft/reply": msgid,
"+draft/react": "l😃l",
RemainingKeys(NotStrRe("label")): AnyOptStr(),
},
fail_msg="No TAGMSG received by the target after sending one out",
)
self.assertNotIn(
@ -361,9 +390,12 @@ class LabeledResponsesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper
# ensure sender correctly receives msg
self.assertMessageMatch(
ms, command="TAGMSG", tags={"label": "12345", **ANYDICT}
ms,
command="TAGMSG",
tags={"label": "12345", "+draft/reply": msgid, **ANYDICT},
)
@pytest.mark.react_tag
@cases.mark_capabilities(
"echo-message", "batch", "labeled-response", "message-tags"
)

View File

@ -2,6 +2,8 @@
https://ircv3.net/specs/extensions/message-tags.html
"""
import pytest
from irctest import cases
from irctest.irc_utils.message_parser import parse_message
from irctest.numerics import ERR_INPUTTOOLONG
@ -9,6 +11,7 @@ from irctest.patma import ANYDICT, ANYSTR, StrRe
class MessageTagsTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
@pytest.mark.arbitrary_client_tags
@cases.mark_capabilities("message-tags")
def testBasic(self):
def getAllMessages():
@ -107,6 +110,7 @@ class MessageTagsTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
self.assertNotIn("cat", msg.tags)
self.assertEqual(alice_msg.tags["msgid"], bob_msg.tags["msgid"])
@pytest.mark.arbitrary_client_tags
@cases.mark_capabilities("message-tags")
@cases.mark_specifications("ircdocs")
def testLengthLimits(self):

View File

@ -74,9 +74,13 @@ class TagsTestCase(cases.BaseServerTestCase):
@cases.mark_capabilities("message-tags")
def testLineTooLong(self):
self.connectClient("bar", capabilities=["message-tags"], skip_if_cap_nak=True)
self.connectClient(
"recver", capabilities=["message-tags"], skip_if_cap_nak=True
)
self.joinChannel(1, "#xyz")
monsterMessage = "@+clientOnlyTagExample=" + "a" * 4096 + " PRIVMSG #xyz hi!"
self.sendLine(1, monsterMessage)
self.assertEqual(self.getMessages(2), [], "overflowing message was relayed")
replies = self.getMessages(1)
self.assertIn(ERR_INPUTTOOLONG, set(reply.command for reply in replies))

View File

@ -109,8 +109,13 @@ class RegressionsTestCase(cases.BaseServerTestCase):
self.sendLine(1, "NICK valid")
replies = {"NOTICE"}
while replies <= {"NOTICE"}:
replies = set(msg.command for msg in self.getMessages(1, synchronize=False))
while replies <= {"NOTICE", "PING"}:
msgs = self.getMessages(1, synchronize=False)
for msg in msgs:
if msg.command == "PING":
# Hi Unreal
self.sendLine(1, "PONG :" + msg.params[0])
replies = set(msg.command for msg in msgs)
self.assertNotIn(ERR_ERRONEUSNICKNAME, replies)
self.assertIn(RPL_WELCOME, replies)