irctest/irctest/server_tests/monitor.py

320 lines
11 KiB
Python
Raw Normal View History

2015-12-25 21:47:11 +00:00
"""
<http://ircv3.net/specs/core/monitor-3.2.html>
"""
from irctest import cases
from irctest.basecontrollers import NotImplementedByController
2021-02-22 18:04:23 +00:00
from irctest.client_mock import NoMessageException
2021-02-22 18:02:13 +00:00
from irctest.numerics import (
RPL_ENDOFMONLIST,
2021-02-22 18:04:23 +00:00
RPL_MONLIST,
2021-02-22 18:02:13 +00:00
RPL_MONOFFLINE,
2021-02-22 18:04:23 +00:00
RPL_MONONLINE,
2021-02-22 18:02:13 +00:00
)
from irctest.patma import ANYSTR, StrRe
2021-02-22 18:02:13 +00:00
2015-12-25 21:47:11 +00:00
2020-05-28 22:40:22 +00:00
class MonitorTestCase(cases.BaseServerTestCase):
2015-12-25 21:47:11 +00:00
def check_server_support(self):
2021-02-22 18:02:13 +00:00
if "MONITOR" not in self.server_support:
raise NotImplementedByController("MONITOR")
2015-12-25 21:47:11 +00:00
def assertMononline(self, client, nick, m=None):
if not m:
m = self.getMessage(client)
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
m,
command="730", # RPL_MONONLINE
params=[ANYSTR, StrRe(nick + "(!.*)?")],
fail_msg="Unexpected notification that monitored nick “{}"
"is online: {msg}",
2021-02-22 18:02:13 +00:00
extra_format=(nick,),
)
2015-12-25 21:47:11 +00:00
def assertMonoffline(self, client, nick, m=None):
if not m:
m = self.getMessage(client)
self.assertMessageMatch(
2021-02-22 18:02:13 +00:00
m,
command="731", # RPL_MONOFFLINE
params=[ANYSTR, nick],
fail_msg="Unexpected notification that monitored nick “{}"
"is offline: {msg}",
2021-02-22 18:02:13 +00:00
extra_format=(nick,),
)
2015-12-25 21:47:11 +00:00
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
2015-12-25 21:47:11 +00:00
def testMonitorOneDisconnected(self):
"""“If any of the targets being added are online, the server will
generate RPL_MONONLINE numerics listing those targets that are
online.
-- <http://ircv3.net/specs/core/monitor-3.2.html#monitor--targettarget2>
"""
2021-02-22 18:02:13 +00:00
self.connectClient("foo")
2015-12-25 21:47:11 +00:00
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR + bar")
self.assertMonoffline(1, "bar")
self.connectClient("bar")
self.assertMononline(1, "bar")
self.sendLine(2, "QUIT :bye")
2015-12-25 21:47:11 +00:00
try:
self.getMessages(2)
except ConnectionResetError:
pass
2021-02-22 18:02:13 +00:00
self.assertMonoffline(1, "bar")
2015-12-25 21:47:11 +00:00
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
2015-12-25 21:47:11 +00:00
def testMonitorOneConnection(self):
2021-02-22 18:02:13 +00:00
self.connectClient("foo")
2015-12-25 21:47:11 +00:00
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR + bar")
2015-12-25 21:47:11 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient("bar")
self.assertMononline(1, "bar")
2015-12-25 21:47:11 +00:00
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
2015-12-25 21:47:11 +00:00
def testMonitorOneConnected(self):
"""“If any of the targets being added are offline, the server will
generate RPL_MONOFFLINE numerics listing those targets that are
online.
-- <http://ircv3.net/specs/core/monitor-3.2.html#monitor--targettarget2>
"""
2021-02-22 18:02:13 +00:00
self.connectClient("foo")
2015-12-25 21:47:11 +00:00
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.connectClient("bar")
self.sendLine(1, "MONITOR + bar")
self.assertMononline(1, "bar")
self.sendLine(2, "QUIT :bye")
2015-12-25 21:47:11 +00:00
try:
self.getMessages(2)
except ConnectionResetError:
pass
2021-02-22 18:02:13 +00:00
self.assertMonoffline(1, "bar")
2015-12-25 21:47:11 +00:00
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
2019-02-14 00:20:46 +00:00
def testMonitorOneConnectionWithQuit(self):
2021-02-22 18:02:13 +00:00
self.connectClient("foo")
2015-12-25 21:47:11 +00:00
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.connectClient("bar")
self.sendLine(1, "MONITOR + bar")
self.assertMononline(1, "bar")
self.sendLine(2, "QUIT :bye")
2015-12-25 21:47:11 +00:00
try:
self.getMessages(2)
except ConnectionResetError:
pass
2021-02-22 18:02:13 +00:00
self.assertMonoffline(1, "bar")
self.connectClient("bar")
self.assertMononline(1, "bar")
2015-12-25 21:47:11 +00:00
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
2015-12-25 21:47:11 +00:00
def testMonitorConnectedAndDisconnected(self):
"""“If any of the targets being added are online, the server will
generate RPL_MONONLINE numerics listing those targets that are
online.
If any of the targets being added are offline, the server will
generate RPL_MONOFFLINE numerics listing those targets that are
online.
-- <http://ircv3.net/specs/core/monitor-3.2.html#monitor--targettarget2>
"""
2021-02-22 18:02:13 +00:00
self.connectClient("foo")
2015-12-25 21:47:11 +00:00
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.connectClient("bar")
self.sendLine(1, "MONITOR + bar,baz")
2015-12-25 21:47:11 +00:00
m1 = self.getMessage(1)
m2 = self.getMessage(1)
commands = {m1.command, m2.command}
2021-02-22 18:02:13 +00:00
self.assertEqual(
commands,
{"730", "731"},
fail_msg="Did not send one 730 (RPL_MONONLINE) and one "
"731 (RPL_MONOFFLINE) after “MONITOR + bar,baz” when “bar” "
"is online and “baz” is offline. Sent this instead: {}",
extra_format=((m1, m2)),
)
if m1.command == "731":
2015-12-25 21:47:11 +00:00
(m1, m2) = (m2, m1)
self.assertMononline(None, "bar", m=m1)
self.assertMonoffline(None, "baz", m=m2)
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
def testUnmonitor(self):
2021-02-22 18:02:13 +00:00
self.connectClient("foo")
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR + bar")
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient("bar")
self.assertMononline(1, "bar")
self.sendLine(1, "MONITOR - bar")
self.assertEqual(
self.getMessages(1),
[],
fail_msg="Got messages after “MONITOR - bar”: {got}",
)
self.sendLine(2, "QUIT :bye")
try:
self.getMessages(2)
except ConnectionResetError:
pass
2021-02-22 18:02:13 +00:00
self.assertEqual(
self.getMessages(1),
[],
fail_msg="Got messages after disconnection of unmonitored " "nick: {got}",
)
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
def testMonitorForbidsMasks(self):
"""“The MONITOR implementation also enhances user privacy by
disallowing subscription to hostmasks, allowing users to avoid
nick-change stalking.
-- <http://ircv3.net/specs/core/monitor-3.2.html#watch-vs-monitor>
For this specification, target MUST be a valid nick as determined
by the IRC daemon.
-- <http://ircv3.net/specs/core/monitor-3.2.html#monitor-command>
"""
2021-02-22 18:02:13 +00:00
self.connectClient("foo")
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR + *!username@localhost")
self.sendLine(1, "MONITOR + *!username@127.0.0.1")
try:
m = self.getMessage(1)
2021-02-22 18:02:13 +00:00
self.assertNotEqual(
m.command,
"731",
m,
fail_msg="Got 731 (RPL_MONOFFLINE) after adding a monitor "
"on a mask: {msg}",
)
except NoMessageException:
pass
2021-02-22 18:02:13 +00:00
self.connectClient("bar")
try:
m = self.getMessage(1)
except NoMessageException:
pass
else:
2021-02-22 18:02:13 +00:00
raise AssertionError(
"Got message after client whose MONITORing "
"was requested via hostmask connected: {}".format(m)
)
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
def testTwoMonitoringOneRemove(self):
"""Tests the following scenario:
* foo MONITORs qux
* bar MONITORs qux
* bar unMONITORs qux
* qux connects.
"""
2021-02-22 18:02:13 +00:00
self.connectClient("foo")
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.connectClient("bar")
self.sendLine(1, "MONITOR + qux")
self.sendLine(2, "MONITOR + qux")
self.getMessages(1)
self.getMessages(2)
2021-02-22 18:02:13 +00:00
self.sendLine(2, "MONITOR - qux")
messages = self.getMessages(2)
2021-02-22 18:02:13 +00:00
self.assertEqual(
messages,
[],
fail_msg="Got response to “MONITOR -”: {}",
extra_format=(messages,),
2021-02-22 18:02:13 +00:00
)
self.connectClient("qux")
self.getMessages(3)
messages = self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.assertNotEqual(
messages,
[],
fail_msg="Received no message after MONITORed client " "connects.",
2021-02-22 18:02:13 +00:00
)
messages = self.getMessages(2)
2021-02-22 18:02:13 +00:00
self.assertEqual(
messages,
[],
fail_msg="Got response to unmonitored client: {}",
extra_format=(messages,),
2021-02-22 18:02:13 +00:00
)
2019-02-17 19:52:34 +00:00
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
2019-02-17 19:52:34 +00:00
def testMonitorList(self):
def checkMonitorSubjects(messages, client_nick, expected_targets):
# collect all the RPL_MONLIST nicks into a set:
result = set()
for message in messages:
if message.command == RPL_MONLIST:
self.assertEqual(message.params[0], client_nick)
2021-02-22 18:02:13 +00:00
result.update(message.params[1].split(","))
2019-02-17 19:52:34 +00:00
# finally, RPL_ENDOFMONLIST should be sent
self.assertEqual(messages[-1].command, RPL_ENDOFMONLIST)
self.assertEqual(messages[-1].params[0], client_nick)
self.assertEqual(result, expected_targets)
2021-02-22 18:02:13 +00:00
self.connectClient("bar")
2019-02-17 19:52:34 +00:00
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR L")
checkMonitorSubjects(self.getMessages(1), "bar", set())
2019-02-17 19:52:34 +00:00
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR + qux")
2019-02-17 19:52:34 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR L")
checkMonitorSubjects(self.getMessages(1), "bar", {"qux"})
2019-02-17 19:52:34 +00:00
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR + bazbat")
2019-02-17 19:52:34 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR L")
checkMonitorSubjects(self.getMessages(1), "bar", {"qux", "bazbat"})
2019-02-17 19:52:34 +00:00
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR - qux")
2019-02-17 19:52:34 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR L")
checkMonitorSubjects(self.getMessages(1), "bar", {"bazbat"})
2020-05-28 22:39:02 +00:00
@cases.mark_specifications("IRCv3")
@cases.mark_isupport("MONITOR")
2020-05-28 22:39:02 +00:00
def testNickChange(self):
# see oragono issue #1076: nickname changes must trigger RPL_MONOFFLINE
2021-02-22 18:02:13 +00:00
self.connectClient("bar")
2020-05-28 22:39:02 +00:00
self.check_server_support()
2021-02-22 18:02:13 +00:00
self.sendLine(1, "MONITOR + qux")
2020-05-28 22:39:02 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.connectClient("baz")
2020-05-28 22:39:02 +00:00
self.getMessages(2)
self.assertEqual(self.getMessages(1), [])
2021-02-22 18:02:13 +00:00
self.sendLine(2, "NICK qux")
2020-05-28 22:39:02 +00:00
self.getMessages(2)
mononline = self.getMessages(1)[0]
self.assertEqual(mononline.command, RPL_MONONLINE)
self.assertEqual(len(mononline.params), 2, mononline.params)
2021-02-22 18:02:13 +00:00
self.assertIn(mononline.params[0], ("bar", "*"))
self.assertEqual(mononline.params[1].split("!")[0], "qux")
2020-05-28 22:39:02 +00:00
# no numerics for a case change
2021-02-22 18:02:13 +00:00
self.sendLine(2, "NICK QUX")
2020-05-28 22:39:02 +00:00
self.getMessages(2)
self.assertEqual(self.getMessages(1), [])
2021-02-22 18:02:13 +00:00
self.sendLine(2, "NICK bazbat")
2020-05-28 22:39:02 +00:00
self.getMessages(2)
monoffline = self.getMessages(1)[0]
# should get RPL_MONOFFLINE with the current unfolded nick
self.assertEqual(monoffline.command, RPL_MONOFFLINE)
self.assertEqual(len(monoffline.params), 2, monoffline.params)
2021-02-22 18:02:13 +00:00
self.assertIn(monoffline.params[0], ("bar", "*"))
self.assertEqual(monoffline.params[1].split("!")[0], "QUX")