1
0
mirror of https://github.com/progval/irctest.git synced 2025-04-08 00:09:46 +00:00

18 Commits

Author SHA1 Message Date
c0678ec350 Enable bouncer tests for Sable 2025-03-29 20:27:39 +01:00
990c0efe31 Fix typos 2025-03-29 17:49:31 +01:00
75bda04241 Split testBouncer into unit tests + add testChannelMessageFromSelf and testDirectMessageFromOther 2025-03-29 17:46:37 +01:00
e9e37f5438 basic test case for KILL () 2025-03-27 05:19:02 -04:00
2b6b666426 Fix irctest for the recent Anope SASL changes. 2025-03-04 07:37:50 +01:00
d218d2f98d Document the versions enc_sha2 and enc_sha256 are used on. 2025-03-04 07:37:50 +01:00
52178a99e5 Install libjansson-dev at build time instead of run time 2025-02-19 19:40:36 +01:00
7f2a631a1a Install libjansson-dev in order to build latest ircd-hybrid 2025-02-18 19:49:02 +01:00
cd9f801b92 Update actions/cache 2025-02-18 19:48:43 +01:00
ccd647ea61 sable: Actually check services are up in tests that rely on them
This should fix some flakiness.
2025-02-14 16:53:03 +01:00
9de64159ba upgrade ergo to go 1.24 () 2025-02-13 08:01:15 +01:00
638f959c95 Fix irctest when using long passwords not supported by bcrypt. () 2025-01-28 19:52:44 +01:00
06e08b52be Make LINKS deterministic for Sable () 2024-12-30 21:07:33 +01:00
54a1ab95ce Relax testLinksWithServices for Sable 2024-12-28 22:54:57 +01:00
3e6d97ae42 Update Sable and make LINKS test support it 2024-12-28 20:30:37 +01:00
00c130d66c Use consistent name for services server 2024-12-28 20:30:37 +01:00
2680502dfe Allow any extban format on InspIRCd. ()
This should stop the mute extban test failing when extbanformat is set to name (which will soon be the default in git master).
2024-11-03 10:24:05 +01:00
d090f5455e Update Sable and enable more test () 2024-10-28 18:15:10 +01:00
24 changed files with 324 additions and 93 deletions

@ -19,7 +19,7 @@ jobs:
python-version: 3.11
- name: Cache dependencies
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
~/.cache

@ -120,6 +120,8 @@ jobs:
path: ircd-hybrid
ref: 8.2.x
repository: ircd-hybrid/ircd-hybrid
- name: Install system dependencies
run: sudo apt-get install atheme-services faketime libjansson-dev
- name: Build Hybrid
run: |
cd $GITHUB_WORKSPACE/ircd-hybrid/
@ -558,7 +560,7 @@ jobs:
repository: ergochat/ergo
- uses: actions/setup-go@v3
with:
go-version: ^1.23.0
go-version: ^1.24.0
- run: go version
- name: Build Ergo
run: |

@ -161,6 +161,8 @@ jobs:
path: ircd-hybrid
ref: 8.2.39
repository: ircd-hybrid/ircd-hybrid
- name: Install system dependencies
run: sudo apt-get install atheme-services faketime libjansson-dev
- name: Build Hybrid
run: |
cd $GITHUB_WORKSPACE/ircd-hybrid/
@ -636,7 +638,7 @@ jobs:
repository: ergochat/ergo
- uses: actions/setup-go@v3
with:
go-version: ^1.23.0
go-version: ^1.24.0
- run: go version
- name: Build Ergo
run: |
@ -1140,7 +1142,7 @@ jobs:
uses: actions/checkout@v4
with:
path: sable
ref: e9701e5e8d0c4f278ddd61ce7285f4918ecf99e9
ref: baed3ef9ac4550dc36a45b758436769e82e8ec58
repository: Libera-Chat/sable
- name: Install rust toolchain
uses: actions-rs/toolchain@v1

@ -84,15 +84,12 @@ LIMNORIA_SELECTORS := \
$(EXTRA_SELECTORS)
# Tests marked with arbitrary_client_tags or react_tag can't pass because Sable does not support client tags yet
# Tests marked with private_chathistory can't pass because Sable does not implement CHATHISTORY for DMs
SABLE_SELECTORS := \
not Ergo \
and not deprecated \
and not strict \
and not arbitrary_client_tags \
and not react_tag \
and not private_chathistory \
and not list and not lusers and not time and not info \
$(EXTRA_SELECTORS)

@ -8,7 +8,7 @@ from irctest.basecontrollers import BaseServicesController, DirectoryBasedContro
TEMPLATE_CONFIG = """
serverinfo {{
name = "services.example.org"
name = "My.Little.Services"
description = "Anope IRC Services"
numeric = "00A"
pid = "services.pid"
@ -66,8 +66,13 @@ options {{
warningtimeout = 4h
}}
module {{ name = "{module_prefix}sasl" }}
module {{ name = "enc_bcrypt" }}
module {{ name = "ns_sasl" }} # since 2.1.13
module {{ name = "sasl" }} # 2.1.2 to 2.1.12
module {{ name = "m_sasl" }} # 2.0 to 2.1.1
module {{ name = "enc_sha2" }} # 2.1
module {{ name = "enc_sha256" }} # 2.0
module {{ name = "ns_cert" }}
"""
@ -123,7 +128,6 @@ class AnopeController(BaseServicesController, DirectoryBasedController):
protocol=protocol,
server_hostname=server_hostname,
server_port=server_port,
module_prefix="" if self.software_version >= (2, 1, 2) else "m_",
)
)

@ -24,7 +24,7 @@ loadmodule "modules/saslserv/plain";
#loadmodule "modules/saslserv/scram";
serverinfo {{
name = "services.example.org";
name = "My.Little.Services";
desc = "Atheme IRC Services";
numeric = "00A";
netname = "testnet";

@ -14,7 +14,7 @@ options {{
network_name unconfigured;
allow_split_ops; # Give ops in empty channels
services_name services.example.org;
services_name My.Little.Services;
// if you need to link more than 1 server, uncomment the following line
servtype hub;
@ -44,7 +44,7 @@ class {{
/* for services */
super {{
"services.example.org";
"My.Little.Services";
}};
@ -57,7 +57,7 @@ class {{
/* our services */
connect {{
name services.example.org;
name My.Little.Services;
host *@127.0.0.1; # unfortunately, masks aren't allowed here
apasswd password;
cpasswd password;
@ -91,7 +91,7 @@ class BahamutController(BaseServerController, DirectoryBasedController):
software_name = "Bahamut"
supported_sasl_mechanisms: Set[str] = set()
supports_sts = False
nickserv = "NickServ@services.example.org"
nickserv = "NickServ@My.Little.Services"
def create_config(self) -> None:
super().create_config()

@ -44,7 +44,7 @@ channel {{
displayed_usercount = 0;
}};
connect "services.example.org" {{
connect "My.Little.Services" {{
host = "localhost"; # Used to validate incoming connection
port = 0; # We don't need servers to connect to services
send_password = "password";
@ -53,14 +53,14 @@ connect "services.example.org" {{
flags = topicburst;
}};
service {{
name = "services.example.org";
name = "My.Little.Services";
}};
privset "omnioper" {{
privs = oper:general, oper:privs, oper:testline, oper:kill, oper:operwall, oper:message,
oper:routing, oper:kline, oper:unkline, oper:xline,
oper:resv, oper:cmodes, oper:mass_notice, oper:wallops,
oper:remoteban,
oper:remoteban, oper:local_kill,
usermode:servnotice, auspex:oper, auspex:hostname, auspex:umodes, auspex:cmodes,
oper:admin, oper:die, oper:rehash, oper:spy, oper:grant;
}};

@ -13,7 +13,7 @@ TEMPLATE_DLK_CONFIG = """\
info {{
SID "00A";
network-name "testnetwork";
services-name "services.example.org";
services-name "My.Little.Services";
admin-email "admin@example.org";
}}

@ -42,7 +42,7 @@ class {{
connectfreq = 5 minutes;
}};
connect {{
name = "services.example.org";
name = "My.Little.Services";
host = "127.0.0.1"; # Used to validate incoming connection
port = 0; # We don't need servers to connect to services
send_password = "password";
@ -50,7 +50,7 @@ connect {{
class = "server";
}};
service {{
name = "services.example.org";
name = "My.Little.Services";
}};
auth {{

@ -19,7 +19,7 @@ TEMPLATE_CONFIG = """
<class
name="ServerOperators"
commands="WALLOPS GLOBOPS"
commands="WALLOPS GLOBOPS KILL"
privs="channels/auspex users/auspex channels/auspex servers/auspex"
>
<type
@ -33,14 +33,15 @@ TEMPLATE_CONFIG = """
class="ServerOperators"
>
<options casemapping="ascii">
<options casemapping="ascii"
extbanformat="any">
# Disable 'NOTICE #chan :*** foo invited bar into the channel-
<security announceinvites="none">
# Services:
<bind address="{services_hostname}" port="{services_port}" type="servers">
<link name="services.example.org"
<link name="My.Little.Services"
ipaddr="{services_hostname}"
port="{services_port}"
allowmask="*"
@ -50,7 +51,7 @@ TEMPLATE_CONFIG = """
<module name="spanningtree">
<module name="hidechans"> # Anope errors when missing
<sasl requiressl="no"
target="services.example.org">
target="My.Little.Services">
# Protocol:
<module name="banexception">

@ -24,7 +24,7 @@ Y:10:90::100:512000:100.100:100.100:
I::{password_field}:::10::
# O:<TARGET Host NAME>:<Password>:<Nickname>:<Port>:<Class>:<Flags>:
O:*:operpassword:operuser::::
O:*:operpassword:operuser:::K:
"""

@ -14,7 +14,7 @@ TEMPLATE_CONFIG = """
{password_field}
[Server]
Name = services.example.org
Name = My.Little.Services
MyPassword = password
PeerPassword = password
Passive = yes # don't connect to it

@ -44,7 +44,7 @@ class {{
connectfreq = 5 minutes;
}};
connect {{
name = "services.example.org";
name = "My.Little.Services";
host = "127.0.0.1"; # Used to validate incoming connection
port = 0; # We don't need servers to connect to services
send_password = "password";
@ -52,7 +52,7 @@ connect {{
class = "server";
}};
service {{
name = "services.example.org";
name = "My.Little.Services";
}};
auth {{

@ -14,6 +14,7 @@ from irctest.basecontrollers import (
NotImplementedByController,
)
from irctest.cases import BaseServerTestCase
from irctest.client_mock import ClientMock
from irctest.exceptions import NoMessageException
from irctest.patma import ANYSTR
@ -496,6 +497,53 @@ class SableServicesController(BaseServicesController):
os.killpg(self.pgroup_id, signal.SIGKILL)
super().kill_proc()
def wait_for_services(self) -> None:
# by default, wait_for_services() connects a user that sends a HELP command
# to NickServ and assumes services are up when it receives a non-ERR_NOSUCHNICK
# reply.
# However, with Sable, NickServ is always up, even when services are not linked,
# so we need to check a different way. We check presence of a non-EXTERNAL
# value to the sasl capability, but LINKS would
if self.services_up:
# Don't check again if they are already available
return
self.server_controller.wait_for_port()
c = ClientMock(name="chkSvs", show_io=True)
c.connect(self.server_controller.hostname, self.server_controller.port)
c.sendLine("NICK chkSvs")
c.sendLine("USER chk chk chk chk")
time.sleep(self.server_controller.sync_sleep_time)
got_end_of_motd = False
while not got_end_of_motd:
for msg in c.getMessages(synchronize=False):
if msg.command == "PING":
# Hi Unreal
c.sendLine("PONG :" + msg.params[0])
if msg.command in ("376", "422"): # RPL_ENDOFMOTD / ERR_NOMOTD
got_end_of_motd = True
timeout = time.time() + 10
while not self.services_up:
if time.time() > timeout:
raise Exception("Timeout while waiting for services")
c.sendLine("CAP LS 302")
msgs = self.getNickServResponse(c, timeout=1)
for msg in msgs:
if msg.command == "CAP":
pass
for token in msg.params[-1].split():
if token.startswith("sasl="):
if "PLAIN" in token.removeprefix("sasl=").split(","):
# SASL PLAIN is available, so services are linked.
self.services_up = True
break
c.sendLine("QUIT")
c.getMessages()
c.disconnect()
def get_irctest_controller_class() -> Type[SableController]:
return SableController

@ -64,7 +64,7 @@ listen {{
options {{ serversonly; }}
}}
link services.example.org {{
link My.Little.Services {{
incoming {{
mask *;
}}
@ -72,11 +72,11 @@ link services.example.org {{
class servers;
}}
ulines {{
services.example.org;
My.Little.Services;
}}
set {{
sasl-server services.example.org;
sasl-server My.Little.Services;
kline-address "example@example.org";
network-name "ExampleNET";
default-server "irc.example.org";

@ -12,9 +12,8 @@ from irctest.patma import ANYSTR, StrRe
@cases.mark_services
class BouncerTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("Ergo")
def testBouncer(self):
"""Test basic bouncer functionality."""
def setUp(self):
super().setUp()
self.controller.registerUser(self, "observer", "observerpassword")
self.controller.registerUser(self, "testuser", "mypassword")
@ -33,13 +32,27 @@ class BouncerTestCase(cases.BaseServerTestCase):
self.sendLine(2, "USER a 0 * a")
self.sendLine(2, "CAP REQ :server-time message-tags")
self.sendLine(2, "CAP END")
messages = self.getMessages(2)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 1)
# should see a regburst for testnick
self.assertMessageMatch(welcomes[0], params=["testnick", ANYSTR])
self._waitForWelcome(2, "testnick")
self.joinChannel(2, "#chan")
def _waitForWelcome(self, client, nick):
"""Waits for numeric 001, and returns every message after that."""
while True:
messages = iter(self.getMessages(client, synchronize=False))
for message in messages:
if message.command == "001":
# should see a regburst for testnick
self.assertMessageMatch(message, params=[nick, ANYSTR])
messages_after_welcome = list(messages)
messages_after_welcome += self.getMessages(client)
# check there is no other 001
for message in messages_after_welcome:
self.assertNotEqual(message.command, "001")
return messages_after_welcome
def _connectClient3(self):
self.addClient()
self.sendLine(3, "CAP LS 302")
self.sendLine(3, "AUTHENTICATE PLAIN")
@ -48,49 +61,37 @@ class BouncerTestCase(cases.BaseServerTestCase):
self.sendLine(3, "USER a 0 * a")
self.sendLine(3, "CAP REQ :server-time message-tags account-tag")
self.sendLine(3, "CAP END")
messages = self.getMessages(3)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 1)
# should see the *same* regburst for testnick
self.assertMessageMatch(welcomes[0], params=["testnick", ANYSTR])
messages = self._waitForWelcome(3, "testnick")
joins = [message for message in messages if message.command == "JOIN"]
# we should be automatically joined to #chan
self.assertMessageMatch(joins[0], params=["#chan"])
# disable multiclient in nickserv
self.sendLine(3, "NS SET MULTICLIENT OFF")
self.getMessages(3)
def _connectClient4(self):
# connect a client similar to 3, but without the message-tags and account-tag
# capabilities, to make sure it does not receive the associated tags
self.addClient()
self.sendLine(4, "CAP LS 302")
self.sendLine(4, "AUTHENTICATE PLAIN")
self.sendLine(4, sasl_plain_blob("testuser", "mypassword"))
self.sendLine(4, "NICK testnick")
self.sendLine(4, "USER a 0 * a")
self.sendLine(4, "CAP REQ :server-time message-tags")
self.sendLine(4, "CAP REQ server-time")
self.sendLine(4, "CAP END")
# with multiclient disabled, we should not be able to attach to the nick
messages = self.getMessages(4)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 0)
errors = [
message for message in messages if message.command == ERR_NICKNAMEINUSE
]
self.assertEqual(len(errors), 1)
# should see the *same* regburst for testnick
self._waitForWelcome(4, "testnick")
self.sendLine(3, "NS SET MULTICLIENT ON")
self.getMessages(3)
self.addClient()
self.sendLine(5, "CAP LS 302")
self.sendLine(5, "AUTHENTICATE PLAIN")
self.sendLine(5, sasl_plain_blob("testuser", "mypassword"))
self.sendLine(5, "NICK testnick")
self.sendLine(5, "USER a 0 * a")
self.sendLine(5, "CAP REQ server-time")
self.sendLine(5, "CAP END")
messages = self.getMessages(5)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 1)
@cases.mark_specifications("Ergo", "Sable")
def testAutomaticResumption(self):
"""Test logging into an account that already has a client joins the client's session"""
self._connectClient3()
@cases.mark_specifications("Ergo", "Sable")
def testChannelMessageFromOther(self):
"""Test that all clients attached to a session get messages sent by someone else
to a channel"""
self._connectClient3()
self._connectClient4()
self.sendLine(1, "@+clientOnlyTag=Value PRIVMSG #chan :hey")
self.getMessages(1)
@ -104,22 +105,84 @@ class BouncerTestCase(cases.BaseServerTestCase):
self.assertEqual(len(messagesforthree), 1)
messagefortwo = messagesfortwo[0]
messageforthree = messagesforthree[0]
messageforfive = self.getMessage(5)
messageforfour = self.getMessage(4)
self.assertMessageMatch(messagefortwo, params=["#chan", "hey"])
self.assertMessageMatch(messageforthree, params=["#chan", "hey"])
self.assertMessageMatch(messageforfive, params=["#chan", "hey"])
self.assertMessageMatch(messageforfour, params=["#chan", "hey"])
self.assertIn("time", messagefortwo.tags)
self.assertIn("time", messageforthree.tags)
self.assertIn("time", messageforfive.tags)
self.assertIn("time", messageforfour.tags)
# 3 has account-tag
self.assertIn("account", messageforthree.tags)
# should get same msgid
self.assertEqual(messagefortwo.tags["msgid"], messageforthree.tags["msgid"])
# 5 only has server-time, shouldn't get account or msgid tags
self.assertNotIn("account", messageforfive.tags)
self.assertNotIn("msgid", messageforfive.tags)
# 4 only has server-time, shouldn't get account or msgid tags
self.assertNotIn("account", messageforfour.tags)
self.assertNotIn("msgid", messageforfour.tags)
@cases.mark_specifications("Ergo", "Sable")
def testChannelMessageFromSelf(self):
"""Test that all clients attached to a session get messages sent by an other client
(TODO: check when the initial sender has echo-message too)"""
self._connectClient3()
self._connectClient4()
self.sendLine(2, "@+clientOnlyTag=Value PRIVMSG #chan :hey")
messagesfortwo = [
msg for msg in self.getMessages(2) if msg.command == "PRIVMSG"
]
messagesforone = [
msg for msg in self.getMessages(1) if msg.command == "PRIVMSG"
]
messagesforthree = [
msg for msg in self.getMessages(3) if msg.command == "PRIVMSG"
]
self.assertEqual(len(messagesforone), 1)
self.assertEqual(len(messagesfortwo), 0) # echo-message not enabled
self.assertEqual(len(messagesforthree), 1)
messageforone = messagesforone[0]
messageforthree = messagesforthree[0]
messageforfour = self.getMessage(4)
self.assertMessageMatch(messageforone, params=["#chan", "hey"])
self.assertMessageMatch(messageforthree, params=["#chan", "hey"])
self.assertMessageMatch(messageforfour, params=["#chan", "hey"])
self.assertIn("time", messageforone.tags)
self.assertIn("time", messageforthree.tags)
self.assertIn("time", messageforfour.tags)
# 3 has account-tag
self.assertIn("account", messageforthree.tags)
# should get same msgid
self.assertEqual(messageforone.tags["msgid"], messageforthree.tags["msgid"])
# 4 only has server-time, shouldn't get account or msgid tags
self.assertNotIn("account", messageforfour.tags)
self.assertNotIn("msgid", messageforfour.tags)
@cases.mark_specifications("Ergo", "Sable")
def testDirectMessageFromOther(self):
"""Test that all clients attached to a session get copies of messages sent
by an other client of that session directly to an other user"""
self._connectClient3()
self._connectClient4()
self.sendLine(1, "PRIVMSG testnick :this is a direct message")
self.getMessages(1)
messagefortwo = [
msg for msg in self.getMessages(2) if msg.command == "PRIVMSG"
][0]
messageforthree = [
msg for msg in self.getMessages(3) if msg.command == "PRIVMSG"
][0]
self.assertEqual(messagefortwo.params, messageforthree.params)
self.assertEqual(messagefortwo.tags["msgid"], messageforthree.tags["msgid"])
@cases.mark_specifications("Ergo", "Sable")
def testDirectMessageFromSelf(self):
"""Test that all clients attached to a session get copies of messages sent
by an other client of that session directly to an other user"""
self._connectClient3()
self._connectClient4()
# test that copies of sent messages go out to other sessions
self.sendLine(2, "PRIVMSG observer :this is a direct message")
self.getMessages(2)
messageForRecipient = [
@ -133,10 +196,20 @@ class BouncerTestCase(cases.BaseServerTestCase):
messageForRecipient.tags["msgid"], copyForOtherSession.tags["msgid"]
)
@cases.mark_specifications("Ergo", "Sable")
def testQuit(self):
"""Test that a single client of a session does not make the whole user quit
(and is generally not visible to anyone else, not even their other sessions),
until the last client quits"""
self._connectClient3()
self._connectClient4()
self.sendLine(2, "QUIT :two out")
quitLines = [msg for msg in self.getMessages(2) if msg.command == "QUIT"]
quitLines = [
msg for msg in self.getMessages(2) if msg.command in ("QUIT", "ERROR")
]
self.assertEqual(len(quitLines), 1)
self.assertMessageMatch(quitLines[0], params=[StrRe(".*two out.*")])
if quitLines[0].command == "QUIT":
self.assertMessageMatch(quitLines[0], params=[StrRe(".*two out.*")])
# neither the observer nor the other attached session should see a quit here
quitLines = [msg for msg in self.getMessages(1) if msg.command == "QUIT"]
self.assertEqual(quitLines, [])
@ -154,13 +227,39 @@ class BouncerTestCase(cases.BaseServerTestCase):
messagesforthree[0], command="PRIVMSG", params=["#chan", "hey again"]
)
self.sendLine(5, "QUIT :five out")
self.getMessages(5)
self.sendLine(4, "QUIT :four out")
self.getMessages(4)
self.sendLine(3, "QUIT :three out")
quitLines = [msg for msg in self.getMessages(3) if msg.command == "QUIT"]
quitLines = [
msg for msg in self.getMessages(3) if msg.command in ("QUIT", "ERROR")
]
self.assertEqual(len(quitLines), 1)
self.assertMessageMatch(quitLines[0], params=[StrRe(".*three out.*")])
if quitLines[0].command == "QUIT":
self.assertMessageMatch(quitLines[0], params=[StrRe(".*three out.*")])
# observer should see *this* quit
quitLines = [msg for msg in self.getMessages(1) if msg.command == "QUIT"]
self.assertEqual(len(quitLines), 1)
self.assertMessageMatch(quitLines[0], params=[StrRe(".*three out.*")])
@cases.mark_specifications("Ergo")
def testDisableAutomaticResumption(self):
# disable multiclient in nickserv
self.sendLine(2, "NS SET MULTICLIENT OFF")
self.getMessages(2)
self.addClient()
self.sendLine(3, "CAP LS 302")
self.sendLine(3, "AUTHENTICATE PLAIN")
self.sendLine(3, sasl_plain_blob("testuser", "mypassword"))
self.sendLine(3, "NICK testnick")
self.sendLine(3, "USER a 0 * a")
self.sendLine(3, "CAP REQ :server-time message-tags")
self.sendLine(3, "CAP END")
# with multiclient disabled, we should not be able to attach to the nick
messages = self.getMessages(3)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 0)
errors = [
message for message in messages if message.command == ERR_NICKNAMEINUSE
]
self.assertEqual(len(errors), 1)

@ -0,0 +1,67 @@
"""
The KILL command (`Modern <https://modern.ircdocs.horse/#kill-message>`__)
"""
from irctest import cases
from irctest.numerics import ERR_NOPRIVILEGES, RPL_YOUREOPER
class KillTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("Modern")
@cases.xfailIfSoftware(["Sable"], "https://github.com/Libera-Chat/sable/issues/154")
def testKill(self):
self.connectClient("ircop", name="ircop")
self.connectClient("alice", name="alice")
self.connectClient("bob", name="bob")
self.sendLine("ircop", "OPER operuser operpassword")
self.assertIn(
RPL_YOUREOPER,
[m.command for m in self.getMessages("ircop")],
fail_msg="OPER failed",
)
self.sendLine("alice", "KILL bob :some arbitrary reason")
self.assertIn(
ERR_NOPRIVILEGES,
[m.command for m in self.getMessages("alice")],
fail_msg="unprivileged KILL not rejected",
)
# bob is not killed
self.getMessages("bob")
self.sendLine("alice", "KILL alice :some arbitrary reason")
self.assertIn(
ERR_NOPRIVILEGES,
[m.command for m in self.getMessages("alice")],
fail_msg="unprivileged KILL not rejected",
)
# alice is not killed
self.getMessages("alice")
# privileged KILL should succeed
self.sendLine("ircop", "KILL alice :some arbitrary reason")
self.getMessages("ircop")
self.assertDisconnected("alice")
self.sendLine("ircop", "KILL bob :some arbitrary reason")
self.getMessages("ircop")
self.assertDisconnected("bob")
@cases.mark_specifications("Ergo")
def testKillOneArgument(self):
self.connectClient("ircop", name="ircop")
self.connectClient("alice", name="alice")
self.sendLine("ircop", "OPER operuser operpassword")
self.assertIn(
RPL_YOUREOPER,
[m.command for m in self.getMessages("ircop")],
fail_msg="OPER failed",
)
# 1-argument kill command, accepted by Ergo and some implementations
self.sendLine("ircop", "KILL alice")
self.getMessages("ircop")
self.assertDisconnected("alice")

@ -3,6 +3,13 @@ from irctest.numerics import ERR_UNKNOWNCOMMAND, RPL_ENDOFLINKS, RPL_LINKS
from irctest.patma import ANYSTR, StrRe
def _server_info_regexp(case: cases.BaseServerTestCase) -> str:
if case.controller.software_name == "Sable":
return ".+"
else:
return "test server"
class LinksTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
def testLinksSingleServer(self):
@ -56,7 +63,7 @@ class LinksTestCase(cases.BaseServerTestCase):
"nick",
"My.Little.Server",
"My.Little.Server",
StrRe("0 (0042 )?test server"),
StrRe(f"0 (0042 )?{_server_info_regexp(self)}"),
],
)
@ -110,7 +117,7 @@ class ServicesLinksTestCase(cases.BaseServerTestCase):
# This server redacts links
return
messages.sort(key=lambda m: m.params[-1])
messages.sort(key=lambda m: tuple(m.params))
self.assertMessageMatch(
messages.pop(0),
@ -119,7 +126,7 @@ class ServicesLinksTestCase(cases.BaseServerTestCase):
"nick",
"My.Little.Server",
"My.Little.Server",
StrRe("0 (0042 )?test server"),
StrRe(f"0 (0042 )?{_server_info_regexp(self)}"),
],
)
self.assertMessageMatch(
@ -127,9 +134,9 @@ class ServicesLinksTestCase(cases.BaseServerTestCase):
command=RPL_LINKS,
params=[
"nick",
"services.example.org",
"My.Little.Services",
"My.Little.Server",
StrRe("1 .+"), # SID instead of description for Anope...
StrRe("[01] .+"), # SID instead of description for Anope...
],
)

@ -221,7 +221,6 @@ class WhoisTestCase(_WhoisTestMixin, cases.BaseServerTestCase):
)
@cases.mark_specifications("RFC2812")
@cases.xfailIfSoftware(["Sable"], "https://github.com/Libera-Chat/sable/issues/101")
def testWhoisMissingUser(self):
"""Test WHOIS on a nonexistent nickname."""
self.connectClient("qux", name="qux")

@ -9,6 +9,7 @@ class Specifications(enum.Enum):
RFC2812 = "RFC2812"
IRCv3 = "IRCv3" # Mark with capabilities whenever possible
Ergo = "Ergo"
SABLE = "Sable"
Ircdocs = "ircdocs"
"""Any document on ircdocs.horse (especially defs.ircdocs.horse),

@ -1,5 +1,5 @@
[mypy]
python_version = 3.8
python_version = 3.9
warn_return_any = True
warn_unused_configs = True

@ -8,6 +8,7 @@ markers =
modern
ircdocs
Ergo
Sable
# misc marks
strict

@ -33,6 +33,9 @@ software:
devel: "8.2.x"
devel_release: null
path: ircd-hybrid
pre_deps:
- name: "Install system dependencies"
run: "sudo apt-get install atheme-services faketime libjansson-dev"
separate_build_job: true
build_script: |
cd $GITHUB_WORKSPACE/ircd-hybrid/
@ -136,7 +139,7 @@ software:
pre_deps:
- uses: actions/setup-go@v3
with:
go-version: '^1.23.0'
go-version: '^1.24.0'
- run: go version
separate_build_job: false
build_script: |
@ -249,7 +252,7 @@ software:
name: Sable
repository: Libera-Chat/sable
refs:
stable: e9701e5e8d0c4f278ddd61ce7285f4918ecf99e9
stable: baed3ef9ac4550dc36a45b758436769e82e8ec58
release: null
devel: master
devel_release: null