Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add various channel mode tests #276

Merged
merged 31 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9af8dfb
add various channel mode tests
slingamn Jun 3, 2024
b37f320
fix isort
slingamn Jun 3, 2024
7536ae5
comply with ngircd nick limit
slingamn Jun 3, 2024
14524bb
strengthen one of the error cases as per Modern
slingamn Jun 3, 2024
87a3a9c
fix 324 parameter assertion
slingamn Jun 3, 2024
bf2633a
relax ERR_NOSUCHCHANNEL requirement for Unreal
slingamn Jun 3, 2024
14f40ea
test existing channel + nonexistent nick
slingamn Jun 3, 2024
1cf7627
add comments explaining the cases
slingamn Jun 3, 2024
689fcb6
add another case
slingamn Jun 3, 2024
e7b723d
raise ngircd nick limit
slingamn Jun 4, 2024
cad1869
use longer nicknames again
slingamn Jun 4, 2024
fb803cb
fix compatibility with bahamut
slingamn Jun 9, 2024
5b797c8
fix compatibility with sable in one case
slingamn Jun 9, 2024
a09430f
mark expected fail for irc2 and sable
slingamn Jun 9, 2024
cb0558e
remove xfail, special-case irc2 and sable instead
slingamn Jun 9, 2024
c6ed675
assert that +int got enabled
slingamn Jun 9, 2024
d896413
split up operator mode test cases
slingamn Jun 9, 2024
f4c4539
move docstring
slingamn Jun 9, 2024
bceb132
tweak special-casing
slingamn Jun 9, 2024
53b7e71
fix inverted comparison
slingamn Jun 9, 2024
6f91d44
ngircd is also weird
slingamn Jun 9, 2024
95be939
strengthen comparison to equality
slingamn Jun 9, 2024
34534df
bump inspircd to 4.0 release candidate
slingamn Jun 9, 2024
cc78133
Revert "bump inspircd to 4.0 release candidate"
slingamn Jun 10, 2024
0ca37d3
mark inspircd case x-fail
slingamn Jun 10, 2024
4b9df39
Update irctest/server_tests/chmodes/modeis.py
slingamn Jun 16, 2024
d1546e6
fix
slingamn Jun 16, 2024
3d5c2cc
Merge remote-tracking branch 'origin/master' into modebug.1
slingamn Jun 16, 2024
3ae33e3
fix lint
slingamn Jun 16, 2024
f3544ca
remove inspircd x-fail
slingamn Jul 2, 2024
209b565
upgrade inspircd stable to v3.17.1
slingamn Jul 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions irctest/controllers/ngircd.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
[Operator]
Name = operuser
Password = operpassword

[Limits]
MaxNickLength = 32 # defaults to 9
"""


Expand Down
65 changes: 65 additions & 0 deletions irctest/server_tests/chmodes/modeis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from irctest import cases
from irctest.numerics import RPL_CHANNELCREATED, RPL_CHANNELMODEIS
from irctest.patma import ANYSTR, ListRemainder


class RplChannelModeIsTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("Modern")
def testChannelModeIs(self):
"""Test RPL_CHANNELMODEIS and RPL_CHANNELCREATED as responses to
`MODE #channel`:
<https://modern.ircdocs.horse/#rplcreationtime-329>
<https://modern.ircdocs.horse/#rplchannelmodeis-324>
"""
expected_numerics = {RPL_CHANNELMODEIS, RPL_CHANNELCREATED}
if self.controller.software_name in ("irc2", "Sable"):
# irc2 and Sable don't use timestamps for conflict resolution,
# consequently they don't store the channel creation timestamp
# and don't send RPL_CHANNELCREATED
expected_numerics = {RPL_CHANNELMODEIS}

self.connectClient("chanop", name="chanop")
self.joinChannel("chanop", "#chan")
# i, n, and t are specified by RFC1459; some of them may be on by default,
# but after this, at least those three should be enabled:
self.sendLine("chanop", "MODE #chan +int")
self.getMessages("chanop")

self.sendLine("chanop", "MODE #chan")
messages = self.getMessages("chanop")
self.assertEqual(expected_numerics, {msg.command for msg in messages})
for message in messages:
if message.command == RPL_CHANNELMODEIS:
# the final parameters are the mode string (e.g. `+int`),
# and then optionally any mode parameters (in case the ircd
# lists a mode that takes a parameter)
self.assertMessageMatch(
message,
command=RPL_CHANNELMODEIS,
params=["chanop", "#chan", ListRemainder(ANYSTR, min_length=1)],
)
final_param = message.params[2]
self.assertEqual(final_param[0], "+")
enabled_modes = list(final_param[1:])
break

self.assertLessEqual({"i", "n", "t"}, set(enabled_modes))

# remove all the modes listed by RPL_CHANNELMODEIS
self.sendLine("chanop", f"MODE #chan -{''.join(enabled_modes)}")
response = self.getMessage("chanop")
self.assertMessageMatch(response, command="MODE", params=["#chan", ANYSTR])
self.assertEqual(response.params[1][0], "-")
slingamn marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(set(response.params[1][1:]), set(enabled_modes))

self.sendLine("chanop", "MODE #chan")
messages = self.getMessages("chanop")
self.assertEqual(expected_numerics, {msg.command for msg in messages})
# all modes have been disabled; the correct representation of this is `+`
for message in messages:
if message.command == RPL_CHANNELMODEIS:
self.assertMessageMatch(
message,
command=RPL_CHANNELMODEIS,
params=["chanop", "#chan", "+"],
)
148 changes: 148 additions & 0 deletions irctest/server_tests/chmodes/operator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from irctest import cases
from irctest.numerics import (
ERR_CHANOPRIVSNEEDED,
ERR_NOSUCHCHANNEL,
ERR_NOSUCHNICK,
ERR_NOTONCHANNEL,
ERR_USERNOTINCHANNEL,
)


class ChannelOperatorModeTestCase(cases.BaseServerTestCase):
"""Test various error and success cases around the channel operator mode:
<https://modern.ircdocs.horse/#channel-operators>
<https://modern.ircdocs.horse/#mode-message>
"""

def setupNicks(self):
"""Set up a standard set of three nicknames and two channels
for testing channel-user MODE interactions."""
# first nick to join the channel is privileged:
self.connectClient("chanop", name="chanop")
self.joinChannel("chanop", "#chan")

self.connectClient("unprivileged", name="unprivileged")
self.joinChannel("unprivileged", "#chan")
self.getMessages("chanop")

self.connectClient("unrelated", name="unrelated")
self.joinChannel("unrelated", "#unrelated")
self.joinChannel("unprivileged", "#unrelated")
self.getMessages("unrelated")

@cases.mark_specifications("Modern")
@cases.xfailIfSoftware(["irc2"], "broken in irc2")
def testChannelOperatorModeSenderPrivsNeeded(self):
"""Test that +o from a channel member without the necessary privileges
fails as expected."""
self.setupNicks()
# sender is a channel member but without the necessary privileges:
self.sendLine("unprivileged", "MODE #chan +o unprivileged")
messages = self.getMessages("unprivileged")
self.assertEqual(len(messages), 1)
self.assertMessageMatch(messages[0], command=ERR_CHANOPRIVSNEEDED)

@cases.mark_specifications("Modern")
@cases.xfailIfSoftware(["InspIRCd"], "fixed in InspIRCd 4.x")
def testChannelOperatorModeTargetNotInChannel(self):
"""Test that +o targeting a user not present in the channel fails
as expected."""
self.setupNicks()
# sender is a chanop, but target nick is not in the channel:
self.sendLine("chanop", "MODE #chan +o unrelated")
messages = self.getMessages("chanop")
self.assertEqual(len(messages), 1)
self.assertMessageMatch(messages[0], command=ERR_USERNOTINCHANNEL)

@cases.mark_specifications("Modern")
def testChannelOperatorModeTargetDoesNotExist(self):
"""Test that +o targeting a nonexistent nick fails as expected."""
self.setupNicks()
# sender is a chanop, but target nick does not exist:
self.sendLine("chanop", "MODE #chan +o nobody")
messages = self.getMessages("chanop")
# ERR_NOSUCHNICK is typical, Bahamut additionally sends ERR_USERNOTINCHANNEL
if self.controller.software_name != "Bahamut":
self.assertEqual(len(messages), 1)
self.assertMessageMatch(messages[0], command=ERR_NOSUCHNICK)
else:
self.assertLessEqual(len(messages), 2)
commands = {message.command for message in messages}
self.assertLessEqual({ERR_NOSUCHNICK}, commands)
self.assertLessEqual(commands, {ERR_NOSUCHNICK, ERR_USERNOTINCHANNEL})
Comment on lines +63 to +71
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


@cases.mark_specifications("Modern")
def testChannelOperatorModeChannelDoesNotExist(self):
"""Test that +o targeting a nonexistent channel fails as expected."""
self.setupNicks()
# target channel does not exist, but target nick does:
self.sendLine("chanop", "MODE #nonexistentchan +o chanop")
messages = self.getMessages("chanop")
self.assertEqual(len(messages), 1)
# Modern: "If <target> is a channel that does not exist on the network,
# the ERR_NOSUCHCHANNEL (403) numeric is returned."
# However, Unreal and ngircd send 401 ERR_NOSUCHNICK here instead:
if self.controller.software_name not in ("UnrealIRCd", "ngIRCd"):
self.assertEqual(messages[0].command, ERR_NOSUCHCHANNEL)
else:
self.assertIn(messages[0].command, [ERR_NOSUCHCHANNEL, ERR_NOSUCHNICK])
Comment on lines +81 to +87
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


@cases.mark_specifications("Modern")
def testChannelOperatorModeChannelAndTargetDoNotExist(self):
"""Test that +o targeting a nonexistent channel and nickname
fails as expected."""
self.setupNicks()
# neither target channel nor target nick exist:
self.sendLine("chanop", "MODE #nonexistentchan +o nobody")
messages = self.getMessages("chanop")
self.assertEqual(len(messages), 1)
self.assertIn(
messages[0].command,
[ERR_NOSUCHCHANNEL, ERR_NOTONCHANNEL, ERR_NOSUCHNICK, ERR_USERNOTINCHANNEL],
)

@cases.mark_specifications("Modern")
def testChannelOperatorModeSenderNonMember(self):
"""Test that +o where the sender is not a channel member
fails as expected."""
self.setupNicks()
# sender is not a channel member, target nick exists and is a channel member:
self.sendLine("chanop", "MODE #unrelated +o unprivileged")
messages = self.getMessages("chanop")
self.assertEqual(len(messages), 1)
self.assertIn(messages[0].command, [ERR_NOTONCHANNEL, ERR_CHANOPRIVSNEEDED])

@cases.mark_specifications("Modern")
def testChannelOperatorModeSenderAndTargetNonMembers(self):
"""Test that +o where neither the sender nor the target is a channel
member fails as expected."""
self.setupNicks()
# sender is not a channel member, target nick exists but is not a channel member:
self.sendLine("chanop", "MODE #unrelated +o chanop")
messages = self.getMessages("chanop")
self.assertEqual(len(messages), 1)
self.assertIn(
messages[0].command,
[ERR_NOTONCHANNEL, ERR_CHANOPRIVSNEEDED, ERR_USERNOTINCHANNEL],
)

@cases.mark_specifications("Modern")
def testChannelOperatorModeSuccess(self):
"""Tests a successful grant of +o in a channel."""
self.setupNicks()

self.sendLine("chanop", "MODE #chan +o unprivileged")
messages = self.getMessages("chanop")
self.assertEqual(len(messages), 1)
self.assertMessageMatch(
messages[0],
command="MODE",
params=["#chan", "+o", "unprivileged"],
)
messages = self.getMessages("unprivileged")
self.assertEqual(len(messages), 1)
self.assertMessageMatch(
messages[0],
command="MODE",
params=["#chan", "+o", "unprivileged"],
)
Loading