irctest/irctest/server_tests/labeled_responses.py

526 lines
17 KiB
Python
Raw Normal View History

2018-02-10 22:56:30 +00:00
"""
`IRCv3 labeled-response <https://ircv3.net/specs/extensions/labeled-response>`_
This specification is a little hard to test because all labels are optional;
so there may be many false positives.
2018-02-10 22:56:30 +00:00
"""
2018-12-28 18:43:01 +00:00
import re
import pytest
2018-02-10 22:56:30 +00:00
from irctest import cases
from irctest.numerics import ERR_UNKNOWNCOMMAND
from irctest.patma import ANYDICT, ANYOPTSTR, NotStrRe, RemainingKeys, StrRe
from irctest.runner import OptionalExtensionNotSupported
2018-02-10 22:56:30 +00:00
2021-02-22 18:02:13 +00:00
2022-04-12 16:48:03 +00:00
class LabeledResponsesTestCase(cases.BaseServerTestCase):
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
2018-02-10 22:56:30 +00:00
def testLabeledPrivmsgResponsesToMultipleClients(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
if int(self.targmax.get("PRIVMSG", "1") or "4") < 3:
raise OptionalExtensionNotSupported("PRIVMSG to multiple targets")
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(2)
2021-02-22 18:02:13 +00:00
self.connectClient(
"carl",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(3)
2021-02-22 18:02:13 +00:00
self.connectClient(
"alice",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(4)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "@label=12345 PRIVMSG bar,carl,alice :hi")
2018-02-10 22:56:30 +00:00
m = self.getMessage(1)
m2 = self.getMessage(2)
m3 = self.getMessage(3)
m4 = self.getMessage(4)
# ensure the label isn't sent to recipients
self.assertMessageMatch(m2, command="PRIVMSG", tags={})
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
m3,
command="PRIVMSG",
tags={},
2021-02-22 18:02:13 +00:00
)
self.assertMessageMatch(m4, command="PRIVMSG", tags={})
2021-02-22 18:02:13 +00:00
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
m, command="BATCH", fail_msg="No BATCH echo received after sending one out"
)
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
2018-02-10 22:56:30 +00:00
def testLabeledPrivmsgResponsesToClient(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(2)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "@label=12345 PRIVMSG bar :hi")
2018-02-10 22:56:30 +00:00
m = self.getMessage(1)
m2 = self.getMessage(2)
# ensure the label isn't sent to recipient
self.assertMessageMatch(m2, command="PRIVMSG", tags={})
2021-02-22 18:02:13 +00:00
self.assertMessageMatch(m, command="PRIVMSG", tags={"label": "12345"})
2021-02-22 18:02:13 +00:00
@pytest.mark.react_tag
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
2018-02-10 22:56:30 +00:00
def testLabeledPrivmsgResponsesToChannel(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(2)
# join channels
2021-02-22 18:02:13 +00:00
self.sendLine(1, "JOIN #test")
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(2, "JOIN #test")
2018-02-10 22:56:30 +00:00
self.getMessages(2)
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(
1, "@label=12345;+draft/reply=123;+draft/react=l😃l PRIVMSG #test :hi"
)
2018-02-10 22:56:30 +00:00
ms = self.getMessage(1)
mt = self.getMessage(2)
# ensure the label isn't sent to recipient
self.assertMessageMatch(mt, command="PRIVMSG", tags={})
2018-02-10 22:56:30 +00:00
# ensure sender correctly receives msg
self.assertMessageMatch(ms, command="PRIVMSG", tags={"label": "12345"})
2021-02-22 18:02:13 +00:00
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
2018-02-10 22:56:30 +00:00
def testLabeledPrivmsgResponsesToSelf(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "@label=12345 PRIVMSG foo :hi")
2018-02-10 22:56:30 +00:00
m1 = self.getMessage(1)
m2 = self.getMessage(1)
number_of_labels = 0
for m in [m1, m2]:
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
m,
command="PRIVMSG",
fail_msg="Got a message back that wasn't a PRIVMSG",
)
if "label" in m.tags:
2018-02-10 22:56:30 +00:00
number_of_labels += 1
2021-02-22 18:02:13 +00:00
self.assertEqual(
m.tags["label"],
"12345",
m,
fail_msg=(
"Echo'd label doesn't match the label we sent "
"(should be '12345'): {msg}"
),
2021-02-22 18:02:13 +00:00
)
self.assertEqual(
number_of_labels,
1,
m1,
fail_msg=(
"When sending a PRIVMSG to self with echo-message, "
"we only expect one message to contain the label. "
"Instead, {} messages had the label"
).format(number_of_labels),
2021-02-22 18:02:13 +00:00
)
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
2018-02-10 22:56:30 +00:00
def testLabeledNoticeResponsesToClient(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(2)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "@label=12345 NOTICE bar :hi")
2018-02-10 22:56:30 +00:00
m = self.getMessage(1)
m2 = self.getMessage(2)
# ensure the label isn't sent to recipient
self.assertMessageMatch(m2, command="NOTICE", tags={})
2021-02-22 18:02:13 +00:00
self.assertMessageMatch(m, command="NOTICE", tags={"label": "12345"})
2021-02-22 18:02:13 +00:00
@pytest.mark.react_tag
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
2018-02-10 22:56:30 +00:00
def testLabeledNoticeResponsesToChannel(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(2)
# join channels
2021-02-22 18:02:13 +00:00
self.sendLine(1, "JOIN #test")
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(2, "JOIN #test")
2018-02-10 22:56:30 +00:00
self.getMessages(2)
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(
1, "@label=12345;+draft/reply=123;+draft/react=l😃l NOTICE #test :hi"
)
2018-02-10 22:56:30 +00:00
ms = self.getMessage(1)
mt = self.getMessage(2)
# ensure the label isn't sent to recipient
self.assertMessageMatch(mt, command="NOTICE", tags={})
2018-02-10 22:56:30 +00:00
# ensure sender correctly receives msg
self.assertMessageMatch(ms, command="NOTICE", tags={"label": "12345"})
2021-02-22 18:02:13 +00:00
@cases.mark_capabilities("echo-message", "batch", "labeled-response")
2018-02-10 22:56:30 +00:00
def testLabeledNoticeResponsesToSelf(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "@label=12345 NOTICE foo :hi")
2018-02-10 22:56:30 +00:00
m1 = self.getMessage(1)
m2 = self.getMessage(1)
number_of_labels = 0
for m in [m1, m2]:
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
m, command="NOTICE", fail_msg="Got a message back that wasn't a NOTICE"
)
if "label" in m.tags:
2018-02-10 22:56:30 +00:00
number_of_labels += 1
2021-02-22 18:02:13 +00:00
self.assertEqual(
m.tags["label"],
"12345",
m,
fail_msg=(
"Echo'd label doesn't match the label we sent "
"(should be '12345'): {msg}"
),
2021-02-22 18:02:13 +00:00
)
self.assertEqual(
number_of_labels,
1,
m1,
fail_msg=(
"When sending a NOTICE to self with echo-message, "
"we only expect one message to contain the label. "
"Instead, {} messages had the label"
).format(number_of_labels),
2021-02-22 18:02:13 +00:00
)
@pytest.mark.react_tag
@cases.mark_capabilities(
"echo-message", "batch", "labeled-response", "message-tags"
)
2018-02-10 22:56:30 +00:00
def testLabeledTagMsgResponsesToClient(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response", "message-tags"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar",
capabilities=["echo-message", "batch", "labeled-response", "message-tags"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(2)
# 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"
)
2018-02-10 22:56:30 +00:00
m = self.getMessage(1)
m2 = self.getMessage(2)
# ensure the label isn't sent to recipient
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
m2,
command="TAGMSG",
tags={
"+draft/reply": msgid,
"+draft/react": "l😃l",
RemainingKeys(NotStrRe("label")): ANYOPTSTR,
},
2021-02-22 18:02:13 +00:00
)
self.assertNotIn(
"label",
m2.tags,
m2,
fail_msg=(
"When sending a TAGMSG with a label, "
"the target user shouldn't receive the label "
"(only the sending user should): {msg}"
),
2021-02-22 18:02:13 +00:00
)
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
m,
command="TAGMSG",
tags={
"label": "12345",
"+draft/reply": msgid,
"+draft/react": "l😃l",
**ANYDICT,
},
2021-02-22 18:02:13 +00:00
)
@pytest.mark.react_tag
@cases.mark_capabilities(
"echo-message", "batch", "labeled-response", "message-tags"
)
2018-02-10 22:56:30 +00:00
def testLabeledTagMsgResponsesToChannel(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response", "message-tags"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar",
capabilities=["echo-message", "batch", "labeled-response", "message-tags"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(2)
# join channels
2021-02-22 18:02:13 +00:00
self.sendLine(1, "JOIN #test")
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(2, "JOIN #test")
2018-02-10 22:56:30 +00:00
self.getMessages(2)
self.getMessages(1)
# 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"
)
2018-02-10 22:56:30 +00:00
ms = self.getMessage(1)
mt = self.getMessage(2)
# ensure the label isn't sent to recipient
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
mt,
command="TAGMSG",
tags={
"+draft/reply": msgid,
"+draft/react": "l😃l",
RemainingKeys(NotStrRe("label")): ANYOPTSTR,
},
2021-02-22 18:02:13 +00:00
fail_msg="No TAGMSG received by the target after sending one out",
)
self.assertNotIn(
"label",
mt.tags,
mt,
fail_msg=(
"When sending a TAGMSG with a label, "
"the target user shouldn't receive the label "
"(only the sending user should): {msg}"
),
2021-02-22 18:02:13 +00:00
)
2018-02-10 22:56:30 +00:00
# ensure sender correctly receives msg
self.assertMessageMatch(
ms,
command="TAGMSG",
tags={"label": "12345", "+draft/reply": msgid, **ANYDICT},
2021-02-22 18:02:13 +00:00
)
@pytest.mark.react_tag
@cases.mark_capabilities(
"echo-message", "batch", "labeled-response", "message-tags"
)
2018-02-10 22:56:30 +00:00
def testLabeledTagMsgResponsesToSelf(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"foo",
capabilities=["echo-message", "batch", "labeled-response", "message-tags"],
2021-02-22 18:02:13 +00:00
skip_if_cap_nak=True,
)
2018-02-10 22:56:30 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "@label=12345;+draft/reply=123;+draft/react=l😃l TAGMSG foo")
2018-02-10 22:56:30 +00:00
m1 = self.getMessage(1)
m2 = self.getMessage(1)
number_of_labels = 0
for m in [m1, m2]:
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
m, command="TAGMSG", fail_msg="Got a message back that wasn't a TAGMSG"
)
if "label" in m.tags:
2018-02-10 22:56:30 +00:00
number_of_labels += 1
2021-02-22 18:02:13 +00:00
self.assertEqual(
m.tags["label"],
"12345",
m,
fail_msg=(
"Echo'd label doesn't match the label we sent "
"(should be '12345'): {msg}"
),
2021-02-22 18:02:13 +00:00
)
self.assertEqual(
number_of_labels,
1,
m1,
fail_msg=(
"When sending a TAGMSG to self with echo-message, "
"we only expect one message to contain the label. "
"Instead, {} messages had the label"
).format(number_of_labels),
2021-02-22 18:02:13 +00:00
)
@cases.mark_capabilities("batch", "labeled-response", "message-tags", "server-time")
2018-12-28 18:43:01 +00:00
def testBatchedJoinMessages(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar",
capabilities=["batch", "labeled-response", "message-tags", "server-time"],
skip_if_cap_nak=True,
)
2018-12-28 18:43:01 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "@label=12345 JOIN #xyz")
2018-12-28 18:43:01 +00:00
m = self.getMessages(1)
# we expect at least join and names lines, which must be batched
self.assertGreaterEqual(len(m), 3)
# valid BATCH start line:
batch_start = m[0]
self.assertMessageMatch(
batch_start,
command="BATCH",
params=[StrRe(r"\+.*"), "labeled-response"],
2021-02-22 18:02:13 +00:00
)
2018-12-28 18:43:01 +00:00
batch_id = batch_start.params[0][1:]
# batch id MUST be alphanumerics and hyphens
2021-02-22 18:02:13 +00:00
self.assertTrue(
re.match(r"^[A-Za-z0-9\-]+$", batch_id) is not None,
"batch id must be alphanumerics and hyphens, got %r" % (batch_id,),
)
self.assertEqual(batch_start.tags.get("label"), "12345")
2018-12-28 18:43:01 +00:00
# valid BATCH end line
batch_end = m[-1]
self.assertMessageMatch(batch_end, command="BATCH", params=["-" + batch_id])
2018-12-28 18:43:01 +00:00
# messages must have the BATCH tag
for message in m[1:-1]:
2021-02-22 18:02:13 +00:00
self.assertEqual(message.tags.get("batch"), batch_id)
2018-12-28 18:43:01 +00:00
@cases.mark_capabilities("labeled-response")
2018-12-28 18:43:01 +00:00
def testNoBatchForSingleMessage(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar", capabilities=["batch", "labeled-response"], skip_if_cap_nak=True
2021-02-22 18:02:13 +00:00
)
2018-12-28 18:43:01 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "@label=98765 PING adhoctestline")
# no BATCH should be initiated for a one-line response,
# it should just be labeled
m = self.getMessage(1)
self.assertMessageMatch(m, command="PONG", tags={"label": "98765"})
2021-02-22 18:02:13 +00:00
self.assertEqual(m.params[-1], "adhoctestline")
@cases.mark_capabilities("labeled-response")
2019-02-21 03:48:48 +00:00
def testEmptyBatchForNoResponse(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar", capabilities=["batch", "labeled-response"], skip_if_cap_nak=True
2021-02-22 18:02:13 +00:00
)
2019-02-21 03:48:48 +00:00
self.getMessages(1)
# PONG never receives a response
2021-02-22 18:02:13 +00:00
self.sendLine(1, "@label=98765 PONG adhoctestline")
2019-02-21 03:48:48 +00:00
2020-01-28 02:12:33 +00:00
# labeled-response: "Servers MUST respond with a labeled
2019-06-13 06:11:26 +00:00
# `ACK` message when a client sends a labeled command that normally
# produces no response."
2019-02-21 03:48:48 +00:00
ms = self.getMessages(1)
2019-06-13 06:11:26 +00:00
self.assertEqual(len(ms), 1)
ack = ms[0]
2019-02-21 03:48:48 +00:00
self.assertMessageMatch(ack, command="ACK", tags={"label": "98765"})
@cases.mark_capabilities("labeled-response")
def testUnknownCommand(self):
self.connectClient(
"bar", capabilities=["batch", "labeled-response"], skip_if_cap_nak=True
)
# this command doesn't exist, but the error response should still
# be labeled:
self.sendLine(1, "@label=deadbeef NONEXISTENT_COMMAND")
ms = self.getMessages(1)
self.assertEqual(len(ms), 1)
unknowncommand = ms[0]
self.assertMessageMatch(
unknowncommand, command=ERR_UNKNOWNCOMMAND, tags={"label": "deadbeef"}
)