diff --git a/irctest/controllers/ergo.py b/irctest/controllers/ergo.py index 5655e82..df71787 100644 --- a/irctest/controllers/ergo.py +++ b/irctest/controllers/ergo.py @@ -77,6 +77,9 @@ BASE_CONFIG = { "channel-length": 128, "client-length": 128, "chathistory-maxmessages": 100, + "retention": { + "allow-individual-delete": True, + }, "tagmsg-storage": { "default": False, "whitelist": ["+draft/persist", "+persist"], diff --git a/irctest/server_tests/redaction.py b/irctest/server_tests/redaction.py new file mode 100644 index 0000000..675c5ef --- /dev/null +++ b/irctest/server_tests/redaction.py @@ -0,0 +1,132 @@ +""" +`IRCv3 draft message redaction `_ +""" + +from irctest import cases +from irctest.patma import ANYDICT, ANYSTR, StrRe + +CAPABILITIES = [ + "message-tags", + "echo-message", + "labeled-response", + "draft/message-redaction", +] + + +@cases.mark_specifications("IRCv3") +@cases.mark_capabilities(*CAPABILITIES) +class RedactTestCase(cases.BaseServerTestCase): + def _setupRedactTest(self, redacteeId, redacteeNick): + self.connectClient("chanop", capabilities=CAPABILITIES, skip_if_cap_nak=True) + self.sendLine(1, "JOIN #chan") + self.connectClient("user", capabilities=CAPABILITIES, skip_if_cap_nak=True) + self.sendLine(2, "JOIN #chan") + self.getMessages(2) # synchronize + self.getMessages(1) + + self.sendLine(redacteeId, "@label=1234 PRIVMSG #chan :hello there") + echo = self.getMessage(redacteeId) + self.assertMessageMatch( + echo, + tags={"label": "1234", "msgid": StrRe("[^ ]+"), **ANYDICT}, + prefix=StrRe(redacteeNick + "!.*"), + command="PRIVMSG", + params=["#chan", "hello there"], + ) + msgid = echo.tags["msgid"] + + self.assertMessageMatch( + self.getMessage(3 - redacteeId), + tags={"msgid": msgid, **ANYDICT}, + prefix=StrRe(redacteeNick + "!.*"), + command="PRIVMSG", + params=["#chan", "hello there"], + ) + + return msgid + + def testRelayOpSelfRedact(self): + """Channel op writes a message and redacts it themselves.""" + msgid = self._setupRedactTest(redacteeId=1, redacteeNick="chanop") + + self.sendLine(1, f"REDACT #chan {msgid} :oops") + self.assertMessageMatch( + self.getMessage(1), + prefix=StrRe("chanop!.*"), + command="REDACT", + params=["#chan", msgid, "oops"], + ) + + self.assertMessageMatch( + self.getMessage(2), + prefix=StrRe("chanop!.*"), + command="REDACT", + params=["#chan", msgid, "oops"], + ) + + def testRelayOpRedact(self): + """User writes a message and channel op redacts it.""" + msgid = self._setupRedactTest( + redacteeId=2, + redacteeNick="user", + ) + + self.sendLine(1, f"REDACT #chan {msgid} :spam") + self.assertMessageMatch( + self.getMessage(1), + prefix=StrRe("chanop!.*"), + command="REDACT", + params=["#chan", msgid, "spam"], + ) + + self.assertMessageMatch( + self.getMessage(2), + prefix=StrRe("chanop!.*"), + command="REDACT", + params=["#chan", msgid, "spam"], + ) + + def testRelayUserSelfRedact(self): + """User writes a message and redacts it themselves. + + Servers may either accept or reject this.""" + msgid = self._setupRedactTest(redacteeId=2, redacteeNick="user") + + self.sendLine(2, f"REDACT #chan {msgid} :oops") + + msg = self.getMessage(2) + if msg.command == "REDACT": + self.assertMessageMatch( + msg, + prefix=StrRe("user!.*"), + command="REDACT", + params=["#chan", msgid, "oops"], + ) + + self.assertMessageMatch( + self.getMessage(1), + prefix=StrRe("user!.*"), + command="REDACT", + params=["#chan", msgid, "oops"], + ) + else: + self.assertMessageMatch( + msg, + command="FAIL", + params=["REDACT", "REDACT_FORBIDDEN", "#chan", msgid, ANYSTR], + ) + + self.assertEqual(self.getMessages(1), []) + + def testRejectRedactOtherUser(self): + """Channel op writes a message and a user attempts to redact it.""" + msgid = self._setupRedactTest(redacteeId=1, redacteeNick="chanop") + + self.sendLine(2, f"REDACT #chan {msgid} :oops") + self.assertMessageMatch( + self.getMessage(2), + command="FAIL", + params=["REDACT", "REDACT_FORBIDDEN", "#chan", msgid, ANYSTR], + ) + + self.assertEqual(self.getMessages(1), []) diff --git a/irctest/specifications.py b/irctest/specifications.py index 9c4617b..e853347 100644 --- a/irctest/specifications.py +++ b/irctest/specifications.py @@ -35,6 +35,7 @@ class Capabilities(enum.Enum): EXTENDED_JOIN = "extended-join" EXTENDED_MONITOR = "extended-monitor" LABELED_RESPONSE = "labeled-response" + MESSAGE_REDACTION = "draft/message-redaction" MESSAGE_TAGS = "message-tags" MULTILINE = "draft/multiline" MULTI_PREFIX = "multi-prefix" diff --git a/pytest.ini b/pytest.ini index 375f2bb..4b3f5ef 100644 --- a/pytest.ini +++ b/pytest.ini @@ -26,6 +26,7 @@ markers = extended-join extended-monitor labeled-response + draft/message-redaction message-tags draft/multiline multi-prefix