mirror of
https://github.com/progval/irctest.git
synced 2025-04-06 07:19:54 +00:00
Run Atheme with InspIRCd, to enable tests depending on SASL
This commit is contained in:
3
.github/workflows/inspircd.yml
vendored
3
.github/workflows/inspircd.yml
vendored
@ -31,6 +31,9 @@ jobs:
|
|||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install pytest -r requirements.txt
|
pip install pytest -r requirements.txt
|
||||||
|
|
||||||
|
- name: Install atheme
|
||||||
|
run: sudo apt-get install atheme-services
|
||||||
|
|
||||||
- name: Checkout InspIRCd
|
- name: Checkout InspIRCd
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
|
@ -178,6 +178,7 @@ class BaseServerController(_BaseController):
|
|||||||
_port_wait_interval = 0.1
|
_port_wait_interval = 0.1
|
||||||
port_open = False
|
port_open = False
|
||||||
port: int
|
port: int
|
||||||
|
hostname: str
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
self,
|
self,
|
||||||
@ -186,6 +187,7 @@ class BaseServerController(_BaseController):
|
|||||||
*,
|
*,
|
||||||
password: Optional[str],
|
password: Optional[str],
|
||||||
ssl: bool,
|
ssl: bool,
|
||||||
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]],
|
valid_metadata_keys: Optional[Set[str]],
|
||||||
invalid_metadata_keys: Optional[Set[str]],
|
invalid_metadata_keys: Optional[Set[str]],
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -219,3 +221,6 @@ class BaseServerController(_BaseController):
|
|||||||
self.port_open = True
|
self.port_open = True
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
def wait_for_services(self) -> None:
|
||||||
|
pass
|
||||||
|
@ -458,6 +458,7 @@ class BaseServerTestCase(
|
|||||||
valid_metadata_keys: Set[str] = set()
|
valid_metadata_keys: Set[str] = set()
|
||||||
invalid_metadata_keys: Set[str] = set()
|
invalid_metadata_keys: Set[str] = set()
|
||||||
server_support: Optional[Dict[str, Optional[str]]]
|
server_support: Optional[Dict[str, Optional[str]]]
|
||||||
|
run_services = False
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
super().setUp()
|
super().setUp()
|
||||||
@ -470,6 +471,7 @@ class BaseServerTestCase(
|
|||||||
valid_metadata_keys=self.valid_metadata_keys,
|
valid_metadata_keys=self.valid_metadata_keys,
|
||||||
invalid_metadata_keys=self.invalid_metadata_keys,
|
invalid_metadata_keys=self.invalid_metadata_keys,
|
||||||
ssl=self.ssl,
|
ssl=self.ssl,
|
||||||
|
run_services=self.run_services,
|
||||||
)
|
)
|
||||||
self.clients: Dict[TClientName, client_mock.ClientMock] = {}
|
self.clients: Dict[TClientName, client_mock.ClientMock] = {}
|
||||||
|
|
||||||
@ -484,6 +486,8 @@ class BaseServerTestCase(
|
|||||||
"""Connects a client to the server and adds it to the dict.
|
"""Connects a client to the server and adds it to the dict.
|
||||||
If 'name' is not given, uses the lowest unused non-negative integer."""
|
If 'name' is not given, uses the lowest unused non-negative integer."""
|
||||||
self.controller.wait_for_port()
|
self.controller.wait_for_port()
|
||||||
|
if self.run_services:
|
||||||
|
self.controller.wait_for_services()
|
||||||
if not name:
|
if not name:
|
||||||
new_name: int = (
|
new_name: int = (
|
||||||
max(
|
max(
|
||||||
|
182
irctest/controllers/atheme_services.py
Normal file
182
irctest/controllers/atheme_services.py
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from typing import IO, Any, List, Optional
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import Protocol
|
||||||
|
except ImportError:
|
||||||
|
# Python < 3.8
|
||||||
|
from typing_extensions import Protocol # type: ignore
|
||||||
|
|
||||||
|
import irctest
|
||||||
|
from irctest.basecontrollers import DirectoryBasedController
|
||||||
|
import irctest.cases
|
||||||
|
from irctest.client_mock import ClientMock
|
||||||
|
from irctest.irc_utils.message_parser import Message
|
||||||
|
import irctest.runner
|
||||||
|
|
||||||
|
TEMPLATE_CONFIG = """
|
||||||
|
loadmodule "modules/protocol/inspircd";
|
||||||
|
loadmodule "modules/backend/opensex";
|
||||||
|
loadmodule "modules/crypto/pbkdf2";
|
||||||
|
|
||||||
|
loadmodule "modules/nickserv/main";
|
||||||
|
loadmodule "modules/nickserv/cert";
|
||||||
|
loadmodule "modules/nickserv/register";
|
||||||
|
loadmodule "modules/nickserv/verify";
|
||||||
|
|
||||||
|
loadmodule "modules/saslserv/authcookie";
|
||||||
|
#loadmodule "modules/saslserv/ecdh-x25519-challenge";
|
||||||
|
loadmodule "modules/saslserv/ecdsa-nist256p-challenge";
|
||||||
|
loadmodule "modules/saslserv/external";
|
||||||
|
loadmodule "modules/saslserv/plain";
|
||||||
|
#loadmodule "modules/saslserv/scram";
|
||||||
|
|
||||||
|
serverinfo {{
|
||||||
|
name = "services.example.org";
|
||||||
|
desc = "Atheme IRC Services";
|
||||||
|
numeric = "00A";
|
||||||
|
netname = "testnet";
|
||||||
|
adminname = "no admin";
|
||||||
|
adminemail = "no-admin@example.org";
|
||||||
|
registeremail = "registration@example.org";
|
||||||
|
auth = none; // Disable email check
|
||||||
|
}};
|
||||||
|
|
||||||
|
general {{
|
||||||
|
commit_interval = 5;
|
||||||
|
}};
|
||||||
|
|
||||||
|
uplink "irc.example.com" {{
|
||||||
|
host = "{server_hostname}";
|
||||||
|
port = {server_port};
|
||||||
|
send_password = "password";
|
||||||
|
receive_password = "password";
|
||||||
|
}};
|
||||||
|
|
||||||
|
saslserv {{
|
||||||
|
nick = "SaslServ";
|
||||||
|
}};
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class _Controller(Protocol):
|
||||||
|
# Magic class to make mypy accept AthemeServices as a mixin without actually
|
||||||
|
# inheriting.
|
||||||
|
directory: Optional[str]
|
||||||
|
hostname: str
|
||||||
|
port: int
|
||||||
|
services_proc: subprocess.Popen
|
||||||
|
|
||||||
|
def wait_for_port(self) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def open_file(self, name: str, mode: str = "a") -> IO:
|
||||||
|
...
|
||||||
|
|
||||||
|
def getNickServResponse(self, client: Any) -> List[Message]:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class AthemeServices(DirectoryBasedController):
|
||||||
|
"""Mixin for server controllers that rely on Atheme"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs): # type: ignore
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.services_proc = None
|
||||||
|
|
||||||
|
def run_services(self: _Controller, server_hostname: str, server_port: int) -> None:
|
||||||
|
with self.open_file("services.conf") as fd:
|
||||||
|
fd.write(
|
||||||
|
TEMPLATE_CONFIG.format(
|
||||||
|
server_hostname=server_hostname,
|
||||||
|
server_port=server_port,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.directory
|
||||||
|
self.services_proc = subprocess.Popen(
|
||||||
|
[
|
||||||
|
"atheme-services",
|
||||||
|
"-n", # don't fork
|
||||||
|
"-c",
|
||||||
|
os.path.join(self.directory, "services.conf"),
|
||||||
|
"-l",
|
||||||
|
f"/tmp/services-{server_port}.log",
|
||||||
|
"-p",
|
||||||
|
os.path.join(self.directory, "services.pid"),
|
||||||
|
"-D",
|
||||||
|
self.directory,
|
||||||
|
],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
def kill_proc(self) -> None:
|
||||||
|
super().kill_proc()
|
||||||
|
if self.services_proc is not None:
|
||||||
|
self.services_proc.kill()
|
||||||
|
self.services_proc = None
|
||||||
|
|
||||||
|
def wait_for_services(self: _Controller) -> None:
|
||||||
|
self.wait_for_port()
|
||||||
|
|
||||||
|
c = ClientMock(name="chkNS", show_io=True)
|
||||||
|
c.connect(self.hostname, self.port)
|
||||||
|
c.sendLine("NICK chkNS")
|
||||||
|
c.sendLine("USER chk chk chk chk")
|
||||||
|
c.getMessages(synchronize=False)
|
||||||
|
|
||||||
|
msgs: List[Message] = []
|
||||||
|
while not msgs:
|
||||||
|
c.sendLine("PRIVMSG NickServ :HELP")
|
||||||
|
msgs = self.getNickServResponse(c)
|
||||||
|
if msgs[0].command == "401":
|
||||||
|
# NickServ not available yet
|
||||||
|
pass
|
||||||
|
elif msgs[0].command == "NOTICE":
|
||||||
|
# NickServ is available
|
||||||
|
assert "nickserv" in (msgs[0].prefix or "").lower(), msgs
|
||||||
|
else:
|
||||||
|
assert False, f"unexpected reply from NickServ: {msgs[0]}"
|
||||||
|
|
||||||
|
c.sendLine("QUIT")
|
||||||
|
c.getMessages()
|
||||||
|
c.disconnect()
|
||||||
|
|
||||||
|
def getNickServResponse(self, client: Any) -> List[Message]:
|
||||||
|
"""Wrapper aroung getMessages() that waits longer, because NickServ
|
||||||
|
is queried asynchronously."""
|
||||||
|
msgs: List[Message] = []
|
||||||
|
while not msgs:
|
||||||
|
time.sleep(0.05)
|
||||||
|
msgs = client.getMessages()
|
||||||
|
return msgs
|
||||||
|
|
||||||
|
def registerUser(
|
||||||
|
self,
|
||||||
|
case: irctest.cases.BaseServerTestCase,
|
||||||
|
username: str,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
if not case.run_services:
|
||||||
|
raise ValueError(
|
||||||
|
"Attempted to register a nick, but `run_services` it not True."
|
||||||
|
)
|
||||||
|
assert password
|
||||||
|
if len(password.encode()) > 288:
|
||||||
|
# It's hardcoded at compile-time :(
|
||||||
|
# https://github.com/atheme/atheme/blob/4fa0e03bd3ce2cb6041a339f308616580c5aac29/include/atheme/constants.h#L51
|
||||||
|
raise irctest.runner.NotImplementedByController("Passwords over 288 bytes")
|
||||||
|
client = case.addClient(show_io=True)
|
||||||
|
case.sendLine(client, "NICK " + username)
|
||||||
|
case.sendLine(client, "USER r e g :user")
|
||||||
|
while case.getRegistrationMessage(client).command != "001":
|
||||||
|
pass
|
||||||
|
case.getMessages(client)
|
||||||
|
case.sendLine(client, f"PRIVMSG NickServ :REGISTER {password} foo@example.org")
|
||||||
|
msgs = self.getNickServResponse(case.clients[client])
|
||||||
|
assert "900" in {msg.command for msg in msgs}, msgs
|
||||||
|
case.sendLine(client, "QUIT")
|
||||||
|
case.assertDisconnected(client)
|
@ -59,6 +59,7 @@ class CharybdisController(BaseServerController, DirectoryBasedController):
|
|||||||
*,
|
*,
|
||||||
password: Optional[str],
|
password: Optional[str],
|
||||||
ssl: bool,
|
ssl: bool,
|
||||||
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -66,6 +67,8 @@ class CharybdisController(BaseServerController, DirectoryBasedController):
|
|||||||
raise NotImplementedByController(
|
raise NotImplementedByController(
|
||||||
"Defining valid and invalid METADATA keys."
|
"Defining valid and invalid METADATA keys."
|
||||||
)
|
)
|
||||||
|
if run_services:
|
||||||
|
raise NotImplementedByController("Registration services")
|
||||||
assert self.proc is None
|
assert self.proc is None
|
||||||
self.create_config()
|
self.create_config()
|
||||||
self.port = port
|
self.port = port
|
||||||
|
@ -151,6 +151,7 @@ class ErgoController(BaseServerController, DirectoryBasedController):
|
|||||||
*,
|
*,
|
||||||
password: Optional[str],
|
password: Optional[str],
|
||||||
ssl: bool,
|
ssl: bool,
|
||||||
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
@ -214,6 +215,13 @@ class ErgoController(BaseServerController, DirectoryBasedController):
|
|||||||
# XXX: Move this somewhere else when
|
# XXX: Move this somewhere else when
|
||||||
# https://github.com/ircv3/ircv3-specifications/pull/152 becomes
|
# https://github.com/ircv3/ircv3-specifications/pull/152 becomes
|
||||||
# part of the specification
|
# part of the specification
|
||||||
|
if not case.run_services:
|
||||||
|
# Ergo does not actually need this, but other controllers do, so we
|
||||||
|
# are checking it here as well for tests that aren't tested with other
|
||||||
|
# controllers.
|
||||||
|
raise ValueError(
|
||||||
|
"Attempted to register a nick, but `run_services` it not True."
|
||||||
|
)
|
||||||
client = case.addClient(show_io=False)
|
client = case.addClient(show_io=False)
|
||||||
case.sendLine(client, "CAP LS 302")
|
case.sendLine(client, "CAP LS 302")
|
||||||
case.sendLine(client, "NICK " + username)
|
case.sendLine(client, "NICK " + username)
|
||||||
|
@ -56,6 +56,7 @@ class HybridController(BaseServerController, DirectoryBasedController):
|
|||||||
*,
|
*,
|
||||||
password: Optional[str],
|
password: Optional[str],
|
||||||
ssl: bool,
|
ssl: bool,
|
||||||
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -7,12 +7,38 @@ from irctest.basecontrollers import (
|
|||||||
DirectoryBasedController,
|
DirectoryBasedController,
|
||||||
NotImplementedByController,
|
NotImplementedByController,
|
||||||
)
|
)
|
||||||
|
from irctest.controllers.atheme_services import AthemeServices
|
||||||
|
from irctest.irc_utils.junkdrawer import find_hostname_and_port
|
||||||
|
|
||||||
TEMPLATE_CONFIG = """
|
TEMPLATE_CONFIG = """
|
||||||
|
# Clients:
|
||||||
<bind address="{hostname}" port="{port}" type="clients">
|
<bind address="{hostname}" port="{port}" type="clients">
|
||||||
{ssl_config}
|
{ssl_config}
|
||||||
|
<connect allow="*"
|
||||||
|
resolvehostnames="no" # Faster
|
||||||
|
recvq="40960" # Needs to be larger than a valid message with tags
|
||||||
|
timeout="10" # So tests don't hang too long
|
||||||
|
{password_field}>
|
||||||
|
|
||||||
|
# Services:
|
||||||
|
<bind address="{services_hostname}" port="{services_port}" type="servers">
|
||||||
|
<link name="services.example.org"
|
||||||
|
ipaddr="{services_hostname}"
|
||||||
|
port="{services_port}"
|
||||||
|
allowmask="*"
|
||||||
|
recvpass="password"
|
||||||
|
sendpass="password"
|
||||||
|
>
|
||||||
|
<module name="spanningtree">
|
||||||
|
<module name="services_account">
|
||||||
|
<module name="svshold"> # Atheme raises a warning when missing
|
||||||
|
<sasl requiressl="no"
|
||||||
|
target="services.example.org">
|
||||||
|
|
||||||
|
# Protocol:
|
||||||
<module name="cap">
|
<module name="cap">
|
||||||
<module name="ircv3">
|
<module name="ircv3">
|
||||||
|
<module name="ircv3_accounttag">
|
||||||
<module name="ircv3_batch">
|
<module name="ircv3_batch">
|
||||||
<module name="ircv3_capnotify">
|
<module name="ircv3_capnotify">
|
||||||
<module name="ircv3_ctctags">
|
<module name="ircv3_ctctags">
|
||||||
@ -24,11 +50,11 @@ TEMPLATE_CONFIG = """
|
|||||||
<module name="monitor">
|
<module name="monitor">
|
||||||
<module name="m_muteban"> # for testing mute extbans
|
<module name="m_muteban"> # for testing mute extbans
|
||||||
<module name="namesx"> # For multi-prefix
|
<module name="namesx"> # For multi-prefix
|
||||||
<connect allow="*"
|
<module name="sasl">
|
||||||
resolvehostnames="no" # Faster
|
|
||||||
recvq="40960" # Needs to be larger than a valid message with tags
|
# Misc:
|
||||||
{password_field}>
|
|
||||||
<log method="file" type="*" level="debug" target="/tmp/ircd-{port}.log">
|
<log method="file" type="*" level="debug" target="/tmp/ircd-{port}.log">
|
||||||
|
<server name="irc.example.com" description="testnet" id="000" network="testnet">
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TEMPLATE_SSL_CONFIG = """
|
TEMPLATE_SSL_CONFIG = """
|
||||||
@ -37,10 +63,12 @@ TEMPLATE_SSL_CONFIG = """
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class InspircdController(BaseServerController, DirectoryBasedController):
|
class InspircdController(
|
||||||
|
AthemeServices, BaseServerController, DirectoryBasedController
|
||||||
|
):
|
||||||
software_name = "InspIRCd"
|
software_name = "InspIRCd"
|
||||||
supported_sasl_mechanisms: Set[str] = set()
|
supported_sasl_mechanisms = {"PLAIN"}
|
||||||
supports_str = False
|
supports_sts = False
|
||||||
|
|
||||||
def create_config(self) -> None:
|
def create_config(self) -> None:
|
||||||
super().create_config()
|
super().create_config()
|
||||||
@ -54,6 +82,7 @@ class InspircdController(BaseServerController, DirectoryBasedController):
|
|||||||
*,
|
*,
|
||||||
password: Optional[str],
|
password: Optional[str],
|
||||||
ssl: bool,
|
ssl: bool,
|
||||||
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
@ -64,8 +93,12 @@ class InspircdController(BaseServerController, DirectoryBasedController):
|
|||||||
)
|
)
|
||||||
assert self.proc is None
|
assert self.proc is None
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self.hostname = hostname
|
||||||
self.create_config()
|
self.create_config()
|
||||||
|
(services_hostname, services_port) = find_hostname_and_port()
|
||||||
|
|
||||||
password_field = 'password="{}"'.format(password) if password else ""
|
password_field = 'password="{}"'.format(password) if password else ""
|
||||||
|
|
||||||
if ssl:
|
if ssl:
|
||||||
self.gen_ssl()
|
self.gen_ssl()
|
||||||
ssl_config = TEMPLATE_SSL_CONFIG.format(
|
ssl_config = TEMPLATE_SSL_CONFIG.format(
|
||||||
@ -73,11 +106,14 @@ class InspircdController(BaseServerController, DirectoryBasedController):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ssl_config = ""
|
ssl_config = ""
|
||||||
|
|
||||||
with self.open_file("server.conf") as fd:
|
with self.open_file("server.conf") as fd:
|
||||||
fd.write(
|
fd.write(
|
||||||
TEMPLATE_CONFIG.format(
|
TEMPLATE_CONFIG.format(
|
||||||
hostname=hostname,
|
hostname=hostname,
|
||||||
port=port,
|
port=port,
|
||||||
|
services_hostname=services_hostname,
|
||||||
|
services_port=services_port,
|
||||||
password_field=password_field,
|
password_field=password_field,
|
||||||
ssl_config=ssl_config,
|
ssl_config=ssl_config,
|
||||||
)
|
)
|
||||||
@ -93,6 +129,11 @@ class InspircdController(BaseServerController, DirectoryBasedController):
|
|||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if run_services:
|
||||||
|
self.run_services(
|
||||||
|
server_hostname=services_hostname, server_port=services_port
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_irctest_controller_class() -> Type[InspircdController]:
|
def get_irctest_controller_class() -> Type[InspircdController]:
|
||||||
return InspircdController
|
return InspircdController
|
||||||
|
@ -88,6 +88,7 @@ class MammonController(BaseServerController, DirectoryBasedController):
|
|||||||
*,
|
*,
|
||||||
password: Optional[str],
|
password: Optional[str],
|
||||||
ssl: bool,
|
ssl: bool,
|
||||||
|
run_services: bool,
|
||||||
valid_metadata_keys: Optional[Set[str]] = None,
|
valid_metadata_keys: Optional[Set[str]] = None,
|
||||||
invalid_metadata_keys: Optional[Set[str]] = None,
|
invalid_metadata_keys: Optional[Set[str]] = None,
|
||||||
restricted_metadata_keys: Optional[Set[str]] = None,
|
restricted_metadata_keys: Optional[Set[str]] = None,
|
||||||
|
@ -6,6 +6,8 @@ from irctest import cases
|
|||||||
|
|
||||||
|
|
||||||
class AccountTagTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
class AccountTagTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
|
run_services = True
|
||||||
|
|
||||||
def connectRegisteredClient(self, nick):
|
def connectRegisteredClient(self, nick):
|
||||||
self.addClient()
|
self.addClient()
|
||||||
self.sendLine(2, "CAP LS 302")
|
self.sendLine(2, "CAP LS 302")
|
||||||
|
@ -5,6 +5,8 @@ from irctest.patma import ANYSTR, StrRe
|
|||||||
|
|
||||||
|
|
||||||
class Bouncer(cases.BaseServerTestCase):
|
class Bouncer(cases.BaseServerTestCase):
|
||||||
|
run_services = True
|
||||||
|
|
||||||
@cases.mark_specifications("Ergo")
|
@cases.mark_specifications("Ergo")
|
||||||
def testBouncer(self):
|
def testBouncer(self):
|
||||||
"""Test basic bouncer functionality."""
|
"""Test basic bouncer functionality."""
|
||||||
|
@ -34,6 +34,8 @@ def validate_chathistory_batch(msgs):
|
|||||||
|
|
||||||
|
|
||||||
class ChathistoryTestCase(cases.BaseServerTestCase):
|
class ChathistoryTestCase(cases.BaseServerTestCase):
|
||||||
|
run_services = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def config() -> cases.TestCaseControllerConfig:
|
def config() -> cases.TestCaseControllerConfig:
|
||||||
return cases.TestCaseControllerConfig(chathistory=True)
|
return cases.TestCaseControllerConfig(chathistory=True)
|
||||||
|
@ -3,6 +3,8 @@ from irctest.numerics import ERR_NICKNAMEINUSE, RPL_WELCOME
|
|||||||
|
|
||||||
|
|
||||||
class ConfusablesTestCase(cases.BaseServerTestCase):
|
class ConfusablesTestCase(cases.BaseServerTestCase):
|
||||||
|
run_services = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def config() -> cases.TestCaseControllerConfig:
|
def config() -> cases.TestCaseControllerConfig:
|
||||||
return cases.TestCaseControllerConfig(
|
return cases.TestCaseControllerConfig(
|
||||||
|
@ -6,6 +6,8 @@ from irctest import cases
|
|||||||
|
|
||||||
|
|
||||||
class MetadataTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
class MetadataTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
|
run_services = True
|
||||||
|
|
||||||
def connectRegisteredClient(self, nick):
|
def connectRegisteredClient(self, nick):
|
||||||
self.addClient()
|
self.addClient()
|
||||||
self.sendLine(2, "CAP LS 302")
|
self.sendLine(2, "CAP LS 302")
|
||||||
|
@ -150,18 +150,16 @@ class RegressionsTestCase(cases.BaseServerTestCase):
|
|||||||
self.addClient(2)
|
self.addClient(2)
|
||||||
self.sendLine(2, "NICK alice")
|
self.sendLine(2, "NICK alice")
|
||||||
self.sendLine(2, "USER u s e r")
|
self.sendLine(2, "USER u s e r")
|
||||||
replies = set(msg.command for msg in self.getMessages(2))
|
m = self.getRegistrationMessage(2)
|
||||||
self.assertNotIn(ERR_NICKNAMEINUSE, replies)
|
self.assertMessageMatch(m, command=RPL_WELCOME)
|
||||||
self.assertIn(RPL_WELCOME, replies)
|
|
||||||
self.sendLine(2, "QUIT")
|
self.sendLine(2, "QUIT")
|
||||||
self.assertDisconnected(2)
|
self.assertDisconnected(2)
|
||||||
|
|
||||||
self.addClient(3)
|
self.addClient(3)
|
||||||
self.sendLine(3, "NICK ALICE")
|
self.sendLine(3, "NICK ALICE")
|
||||||
self.sendLine(3, "USER u s e r")
|
self.sendLine(3, "USER u s e r")
|
||||||
replies = set(msg.command for msg in self.getMessages(3))
|
m = self.getRegistrationMessage(3)
|
||||||
self.assertNotIn(ERR_NICKNAMEINUSE, replies)
|
self.assertMessageMatch(m, command=RPL_WELCOME)
|
||||||
self.assertIn(RPL_WELCOME, replies)
|
|
||||||
|
|
||||||
@cases.mark_specifications("RFC1459")
|
@cases.mark_specifications("RFC1459")
|
||||||
def testNickReleaseUnregistered(self):
|
def testNickReleaseUnregistered(self):
|
||||||
|
@ -5,11 +5,15 @@ from irctest.patma import ANYSTR
|
|||||||
|
|
||||||
|
|
||||||
class RegistrationTestCase(cases.BaseServerTestCase):
|
class RegistrationTestCase(cases.BaseServerTestCase):
|
||||||
|
run_services = True
|
||||||
|
|
||||||
def testRegistration(self):
|
def testRegistration(self):
|
||||||
self.controller.registerUser(self, "testuser", "mypassword")
|
self.controller.registerUser(self, "testuser", "mypassword")
|
||||||
|
|
||||||
|
|
||||||
class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
|
run_services = True
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testPlain(self):
|
def testPlain(self):
|
||||||
@ -50,6 +54,34 @@ class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
|||||||
fail_msg="Unexpected reply to correct SASL authentication: {msg}",
|
fail_msg="Unexpected reply to correct SASL authentication: {msg}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cases.mark_specifications("IRCv3")
|
||||||
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
|
def testPlainNonAscii(self):
|
||||||
|
password = "é" * 100
|
||||||
|
authstring = base64.b64encode(
|
||||||
|
b"\x00".join([b"foo", b"foo", password.encode()])
|
||||||
|
).decode()
|
||||||
|
self.controller.registerUser(self, "foo", password)
|
||||||
|
self.addClient()
|
||||||
|
self.requestCapabilities(1, ["sasl"], skip_if_cap_nak=False)
|
||||||
|
self.sendLine(1, "AUTHENTICATE PLAIN")
|
||||||
|
m = self.getRegistrationMessage(1)
|
||||||
|
self.assertMessageMatch(
|
||||||
|
m,
|
||||||
|
command="AUTHENTICATE",
|
||||||
|
params=["+"],
|
||||||
|
fail_msg="Sent “AUTHENTICATE PLAIN”, server should have "
|
||||||
|
"replied with “AUTHENTICATE +”, but instead sent: {msg}",
|
||||||
|
)
|
||||||
|
self.sendLine(1, "AUTHENTICATE " + authstring)
|
||||||
|
m = self.getRegistrationMessage(1)
|
||||||
|
self.assertMessageMatch(
|
||||||
|
m,
|
||||||
|
command="900",
|
||||||
|
params=[ANYSTR, ANYSTR, "foo", ANYSTR],
|
||||||
|
fail_msg="Unexpected reply to correct SASL authentication: {msg}",
|
||||||
|
)
|
||||||
|
|
||||||
@cases.mark_specifications("IRCv3")
|
@cases.mark_specifications("IRCv3")
|
||||||
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
@cases.OptionalityHelper.skipUnlessHasMechanism("PLAIN")
|
||||||
def testPlainNoAuthzid(self):
|
def testPlainNoAuthzid(self):
|
||||||
@ -127,6 +159,8 @@ class SaslTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
|||||||
self.requestCapabilities(1, ["sasl"], skip_if_cap_nak=False)
|
self.requestCapabilities(1, ["sasl"], skip_if_cap_nak=False)
|
||||||
self.sendLine(1, "AUTHENTICATE FOO")
|
self.sendLine(1, "AUTHENTICATE FOO")
|
||||||
m = self.getRegistrationMessage(1)
|
m = self.getRegistrationMessage(1)
|
||||||
|
while m.command == "908": # RPL_SASLMECHS
|
||||||
|
m = self.getRegistrationMessage(1)
|
||||||
self.assertMessageMatch(
|
self.assertMessageMatch(
|
||||||
m,
|
m,
|
||||||
command="904",
|
command="904",
|
||||||
|
@ -14,6 +14,8 @@ from irctest.numerics import (
|
|||||||
|
|
||||||
|
|
||||||
class WhoisTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
class WhoisTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
|
||||||
|
run_services = True
|
||||||
|
|
||||||
@cases.mark_specifications("RFC2812")
|
@cases.mark_specifications("RFC2812")
|
||||||
def testWhoisUser(self):
|
def testWhoisUser(self):
|
||||||
"""Test basic WHOIS behavior"""
|
"""Test basic WHOIS behavior"""
|
||||||
|
@ -14,6 +14,8 @@ def extract_playback_privmsgs(messages):
|
|||||||
|
|
||||||
|
|
||||||
class ZncPlaybackTestCase(cases.BaseServerTestCase):
|
class ZncPlaybackTestCase(cases.BaseServerTestCase):
|
||||||
|
run_services = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def config() -> cases.TestCaseControllerConfig:
|
def config() -> cases.TestCaseControllerConfig:
|
||||||
return cases.TestCaseControllerConfig(chathistory=True)
|
return cases.TestCaseControllerConfig(chathistory=True)
|
||||||
|
Reference in New Issue
Block a user