diff --git a/irctest/controllers/inspircd.py b/irctest/controllers/inspircd.py index b932ec2..106d8fd 100644 --- a/irctest/controllers/inspircd.py +++ b/irctest/controllers/inspircd.py @@ -73,6 +73,7 @@ TEMPLATE_CONFIG = """ # for testing mute extbans # For multi-prefix + # For userhost-in-names # HELP/HELPOP # for the HELP alias diff --git a/irctest/server_tests/cap.py b/irctest/server_tests/cap.py index 95c4027..b6b0d5e 100644 --- a/irctest/server_tests/cap.py +++ b/irctest/server_tests/cap.py @@ -4,11 +4,32 @@ """ from irctest import cases -from irctest.patma import ANYSTR +from irctest.patma import ANYSTR, StrRe from irctest.runner import CapabilityNotSupported, ImplementationChoice class CapTestCase(cases.BaseServerTestCase): + @cases.mark_specifications("IRCv3") + def testInvalidCapSubcommand(self): + """“If no capabilities are active, an empty parameter must be sent.” + -- + """ # noqa + self.addClient() + self.sendLine(1, "CAP NOTACOMMAND") + self.sendLine(1, "PING test123") + m = self.getRegistrationMessage(1) + self.assertTrue( + self.messageDiffers(m, command="PONG", params=[ANYSTR, "test123"]), + "Sending “CAP NOTACOMMAND” as first message got no reply", + ) + self.assertMessageMatch( + m, + command="410", + params=["*", "NOTACOMMAND", ANYSTR], + fail_msg="Sending “CAP NOTACOMMAND” as first message got a reply " + "that is not ERR_INVALIDCAPCMD: {msg}", + ) + @cases.mark_specifications("IRCv3") def testNoReq(self): """Test the server handles gracefully clients which do not send @@ -23,12 +44,206 @@ class CapTestCase(cases.BaseServerTestCase): self.getCapLs(1) self.sendLine(1, "USER foo foo foo :foo") self.sendLine(1, "NICK foo") + + # Make sure the server didn't send anything yet + self.sendLine(1, "CAP LS 302") + self.getCapLs(1) + self.sendLine(1, "CAP END") m = self.getRegistrationMessage(1) self.assertMessageMatch( m, command="001", fail_msg="Expected 001 after sending CAP END, got {msg}." ) + @cases.mark_specifications("IRCv3") + def testReqOne(self): + """Tests requesting a single capability""" + self.addClient(1) + self.sendLine(1, "CAP LS") + self.getCapLs(1) + self.sendLine(1, "USER foo foo foo :foo") + self.sendLine(1, "NICK foo") + self.sendLine(1, "CAP REQ :multi-prefix") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ANYSTR, "ACK", StrRe("multi-prefix ?")], + fail_msg="Expected CAP ACK after sending CAP REQ, got {msg}.", + ) + + self.sendLine(1, "CAP LIST") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ANYSTR, "LIST", StrRe("multi-prefix ?")], + fail_msg="Expected CAP LIST after sending CAP LIST, got {msg}.", + ) + + self.sendLine(1, "CAP END") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, command="001", fail_msg="Expected 001 after sending CAP END, got {msg}." + ) + + @cases.mark_specifications("IRCv3") + @cases.xfailIfSoftware( + ["ngIRCd"], + "ngIRCd does not support userhost-in-names", + ) + def testReqTwo(self): + """Tests requesting two capabilities at once""" + self.addClient(1) + self.sendLine(1, "CAP LS") + self.getCapLs(1) + self.sendLine(1, "USER foo foo foo :foo") + self.sendLine(1, "NICK foo") + self.sendLine(1, "CAP REQ :multi-prefix userhost-in-names") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ANYSTR, "ACK", StrRe("multi-prefix userhost-in-names ?")], + fail_msg="Expected CAP ACK after sending CAP REQ, got {msg}.", + ) + + self.sendLine(1, "CAP LIST") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ + ANYSTR, + "LIST", + StrRe( + "(multi-prefix userhost-in-names|userhost-in-names multi-prefix) ?" + ), + ], + fail_msg="Expected CAP LIST after sending CAP LIST, got {msg}.", + ) + + self.sendLine(1, "CAP END") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, command="001", fail_msg="Expected 001 after sending CAP END, got {msg}." + ) + + @cases.mark_specifications("IRCv3") + @cases.xfailIfSoftware( + ["ngIRCd"], + "ngIRCd does not support userhost-in-names", + ) + def testReqOneThenOne(self): + """Tests requesting two capabilities in different messages""" + self.addClient(1) + self.sendLine(1, "CAP LS") + self.getCapLs(1) + self.sendLine(1, "USER foo foo foo :foo") + self.sendLine(1, "NICK foo") + + self.sendLine(1, "CAP REQ :multi-prefix") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ANYSTR, "ACK", StrRe("multi-prefix ?")], + fail_msg="Expected CAP ACK after sending CAP REQ, got {msg}.", + ) + + self.sendLine(1, "CAP REQ :userhost-in-names") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ANYSTR, "ACK", StrRe("userhost-in-names ?")], + fail_msg="Expected CAP ACK after sending CAP REQ, got {msg}.", + ) + + self.sendLine(1, "CAP LIST") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ + ANYSTR, + "LIST", + StrRe( + "(multi-prefix userhost-in-names|userhost-in-names multi-prefix) ?" + ), + ], + fail_msg="Expected CAP LIST after sending CAP LIST, got {msg}.", + ) + + self.sendLine(1, "CAP END") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, command="001", fail_msg="Expected 001 after sending CAP END, got {msg}." + ) + + @cases.mark_specifications("IRCv3") + @cases.xfailIfSoftware( + ["ngIRCd"], + "ngIRCd does not support userhost-in-names", + ) + def testReqPostRegistration(self): + """Tests requesting more capabilities after CAP END""" + self.addClient(1) + self.sendLine(1, "CAP LS") + self.getCapLs(1) + self.sendLine(1, "USER foo foo foo :foo") + self.sendLine(1, "NICK foo") + + self.sendLine(1, "CAP REQ :multi-prefix") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ANYSTR, "ACK", StrRe("multi-prefix ?")], + fail_msg="Expected CAP ACK after sending CAP REQ, got {msg}.", + ) + + self.sendLine(1, "CAP LIST") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ANYSTR, "LIST", StrRe("multi-prefix ?")], + fail_msg="Expected CAP LIST after sending CAP LIST, got {msg}.", + ) + + self.sendLine(1, "CAP END") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, command="001", fail_msg="Expected 001 after sending CAP END, got {msg}." + ) + + self.getMessages(1) + + self.sendLine(1, "CAP REQ :userhost-in-names") + m = self.getRegistrationMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ANYSTR, "ACK", StrRe("userhost-in-names ?")], + fail_msg="Expected CAP ACK after sending CAP REQ, got {msg}.", + ) + + self.sendLine(1, "CAP LIST") + m = self.getMessage(1) + self.assertMessageMatch( + m, + command="CAP", + params=[ + ANYSTR, + "LIST", + StrRe( + "(multi-prefix userhost-in-names|userhost-in-names multi-prefix) ?" + ), + ], + fail_msg="Expected CAP LIST after sending CAP LIST, got {msg}.", + ) + @cases.mark_specifications("IRCv3") def testReqUnavailable(self): """Test the server handles gracefully clients which request @@ -45,7 +260,7 @@ class CapTestCase(cases.BaseServerTestCase): self.assertMessageMatch( m, command="CAP", - params=[ANYSTR, "NAK", "foo"], + params=[ANYSTR, "NAK", StrRe("foo ?")], fail_msg="Expected CAP NAK after requesting non-existing " "capability, got {msg}.", ) @@ -78,10 +293,6 @@ class CapTestCase(cases.BaseServerTestCase): ) @cases.mark_specifications("IRCv3") - @cases.xfailIfSoftware( - ["UnrealIRCd"], - "UnrealIRCd sends a trailing space on CAP NAK: https://github.com/unrealircd/unrealircd/pull/148", - ) def testNakWhole(self): """“The capability identifier set must be accepted as a whole, or rejected entirely.” @@ -123,16 +334,12 @@ class CapTestCase(cases.BaseServerTestCase): self.assertMessageMatch( m, command="CAP", - params=[ANYSTR, "ACK", "multi-prefix"], + params=[ANYSTR, "ACK", StrRe("multi-prefix ?")], fail_msg="Expected “CAP ACK :multi-prefix” after " "sending “CAP REQ :multi-prefix”, but got {msg}.", ) @cases.mark_specifications("IRCv3") - @cases.xfailIfSoftware( - ["UnrealIRCd"], - "UnrealIRCd sends a trailing space on CAP NAK: https://github.com/unrealircd/unrealircd/pull/148", - ) def testCapRemovalByClient(self): """Test CAP LIST and removal of caps via CAP REQ :-tagname.""" cap1 = "echo-message" @@ -167,17 +374,19 @@ class CapTestCase(cases.BaseServerTestCase): m = self.getMessage(1) self.assertIn("time", m.tags, m) - # remove the server-time cap + # remove the multi-prefix cap self.sendLine(1, f"CAP REQ :-{cap2}") m = self.getMessage(1) # Must be either ACK or NAK - if self.messageDiffers(m, command="CAP", params=[ANYSTR, "ACK", f"-{cap2}"]): + if self.messageDiffers( + m, command="CAP", params=[ANYSTR, "ACK", StrRe(f"-{cap2} ?")] + ): self.assertMessageMatch( - m, command="CAP", params=[ANYSTR, "NAK", f"-{cap2}"] + m, command="CAP", params=[ANYSTR, "NAK", StrRe(f"-{cap2} ?")] ) raise ImplementationChoice(f"Does not support CAP REQ -{cap2}") - # server-time should be disabled + # multi-prefix should be disabled self.sendLine(1, "CAP LIST") messages = self.getMessages(1) cap_list = [m for m in messages if m.command == "CAP"][0]