Authselect system tests use pytest, pytest-mh and :mod:`authselect_test_framework`.
Each test that requires remote hosts must be marked with a topology marker. Use predefined profiles from :class:`authselect_test_framework.profiles.Profile`:
import pytest
from authselect_test_framework.roles.client import Client
from authselect_test_framework.profiles import Profile
@pytest.mark.topology(Profile.Local)
def test_example(client: Client):
client.authselect.select("local")
assert True| Profile | Hosts | Use case |
|---|---|---|
| :attr:`~authselect_test_framework.profiles.Profile.Local` | client | Local profile, presets, CLI tests, and local-only PAM tests |
| :attr:`~authselect_test_framework.profiles.Profile.SSSD` | client + ipa | SSSD profile with FreeIPA provider |
| :attr:`~authselect_test_framework.profiles.Profile.Winbind` | client + samba | Winbind profile against a Samba AD domain |
Use this table to decide where a test belongs:
| Topology | File | When to use |
|---|---|---|
| :attr:`~authselect_test_framework.profiles.ProfileGroup.AnyProfile` | test_profiles.py |
Feature behaves the same on local, SSSD, and Winbind |
| :attr:`~authselect_test_framework.profiles.ProfileGroup.AnyProvider` | test_profiles.py |
Feature requires a remote directory provider (SSSD or Winbind) but not local |
| :attr:`~authselect_test_framework.profiles.Profile.Local` | test_local.py |
PAM behaviour only testable on the local profile (e.g. when pam_sss.so would intercept before pam_unix.so on sssd/winbind) |
| :attr:`~authselect_test_framework.profiles.Profile.SSSD` | test_sssd.py |
SSSD-specific feature (e.g. with-sudo, with-smartcard) |
| :attr:`~authselect_test_framework.profiles.Profile.Winbind` | test_winbind.py |
Winbind-specific feature (e.g. with-krb5) |
Profile.Local |
test_cli.py |
CLI command output and exit codes only — no functional behaviour |
Tests in test_profiles.py marked with
:attr:`~authselect_test_framework.profiles.ProfileGroup.AnyProfile` run once per
profile (local, sssd, winbind). Use client.profile to get the current profile
and client.profile as the profile string argument to authselect.select().
From test_profiles__with_ecryptfs:
import pytest
from authselect_test_framework.roles.client import Client
from authselect_test_framework.roles.generic import GenericProvider
from authselect_test_framework.profiles import ProfileGroup
@pytest.mark.topology(ProfileGroup.AnyProfile)
def test_profiles__with_ecryptfs(client: Client, provider: GenericProvider):
client.authselect.select(client.profile, ["with-ecryptfs"])
system_auth = client.fs.read("/etc/pam.d/system-auth")
assert "pam_ecryptfs.so" in system_auth, "system-auth should include pam_ecryptfs!"
client.authselect.disable_feature(["with-ecryptfs"])
system_auth = client.fs.read("/etc/pam.d/system-auth")
assert "pam_ecryptfs.so" not in system_auth, (
"system-auth should not include pam_ecryptfs after feature is disabled!"
)Functional shared tests that need users or an identity service use a helper
start_identity_service (defined at the top of test_profiles.py) and
branch on client.profile only where the behaviour genuinely differs:
From test_profiles__with_faillock:
import pytest
from authselect_test_framework.roles.client import Client
from authselect_test_framework.roles.generic import GenericProvider
from authselect_test_framework.profiles import Profile, ProfileGroup
from authselect_test_framework.utils.pam import PAMFaillockUtils
from pytest_mh import mh_utility
@pytest.mark.topology(ProfileGroup.AnyProfile)
def test_profiles__with_faillock(client: Client, provider: GenericProvider):
provider.user("user-1").add(uid=10001, gid=10001, home="/home/user-1", shell="/bin/bash")
start_identity_service(client, provider)
faillock = PAMFaillockUtils(client.host, client.fs)
with mh_utility(faillock):
faillock.config_set({"deny": "3", "unlock_time": "300"})
client.authselect.select(client.profile, ["with-faillock"])
faillock.reset_user("user-1")
if client.profile is not Profile.Local:
assert client.tools.id("user-1") is not None, "'user-1' was not found!"
assert client.auth.su.password("user-1", password="Secret123"), (
"initial su authentication should succeed!"
)
for _ in range(3):
client.auth.su.password("user-1", password="BadSecret123")
assert not client.auth.su.password("user-1", password="Secret123"), (
"user should be locked out after failed attempts!"
)
client.authselect.disable_feature(["with-faillock"])
assert client.auth.su.password("user-1", password="Secret123"), (
"su authentication should succeed after 'with-faillock' was disabled!"
)Notes:
client.profile— current :class:`~authselect_test_framework.profiles.Profile` member (Profile.Local,Profile.SSSD, orProfile.Winbind)- Pass
client.profiledirectly toauthselect.select()— it is accepted as the profile string argument provider.user(...).add()creates users on the active provider (local client, IPA, or Samba)start_identity_service(client, provider)starts SSSD or Winbind as needed; call it beforeauthselect.select()so service start is a setup stepselect()callsassert_selected()internally — never callassert_selected()again afterselect(), and never useis_feature_enabled()as a standalone assertion
:attr:`~authselect_test_framework.profiles.ProfileGroup.AnyProvider` covers SSSD and Winbind only. Use it when the test requires a central directory (users in IPA or Samba AD) but the feature is otherwise profile-agnostic.
From test_profiles__with_group_merging:
import pytest
from authselect_test_framework.roles.client import Client
from authselect_test_framework.roles.generic import GenericProvider
from authselect_test_framework.profiles import ProfileGroup
@pytest.mark.topology(ProfileGroup.AnyProvider)
def test_profiles__with_group_merging(client: Client, provider: GenericProvider):
client.local.group("group").add(gid=123456)
provider.user("user-1").add(uid=10001, gid=10001, home="/home/user-1", shell="/bin/bash")
provider.group("group").add(gid=123456).add_member(provider.user("user-1"))
start_identity_service(client, provider)
client.authselect.select(client.profile, ["with-group-merging"])
group = client.tools.getent.group("group")
assert group is not None, "merged group lookup should find group!"
assert "user-1" in group.members, "user should be a member of the merged group lookup!"
files_group = client.tools.getent.group("group", service="files")
assert "user-1" not in files_group.members, (
"user should not be a member of the files-only group lookup!"
)
client.authselect.disable_feature(["with-group-merging"])
nsswitch = client.fs.read("/etc/nsswitch.conf")
group_line = next(line for line in nsswitch.splitlines() if line.startswith("group:"))
assert "[SUCCESS=merge]" not in group_line, (
"group nsswitch entry should not use 'SUCCESS=merge' when feature is disabled!"
)Some features can only be tested meaningfully on the local profile. For example,
without-nullok removes nullok from pam_unix.so, but on the sssd and
winbind profiles pam_sss.so short-circuits authentication with
default=bad before pam_unix.so is ever reached for local users. Put such
tests in test_local.py:
From test_local__without_nullok:
import pytest
from authselect_test_framework.roles.client import Client
from authselect_test_framework.profiles import Profile
@pytest.mark.topology(Profile.Local)
def test_local__without_nullok(client: Client):
client.user("user-3").add(uid=10003, gid=10003, home="/home/user-3", shell="/bin/bash", password="")
client.host.conn.run("passwd -d user-3")
client.authselect.select("local", ["without-nullok"])
assert not client.auth.su.password("user-3", password=""), (
"login with empty password should be denied with 'without-nullok' enabled!"
)
client.authselect.disable_feature(["without-nullok"])
system_auth = client.fs.read("/etc/pam.d/system-auth")
assert "nullok" in system_auth, (
"'pam_unix.so' should have 'nullok' after disabling 'without-nullok'!"
)From test_sssd__with_sudo:
import pytest
from authselect_test_framework.roles.client import Client
from authselect_test_framework.roles.generic import GenericProvider
from authselect_test_framework.profiles import Profile
@pytest.mark.topology(Profile.SSSD)
def test_sssd__with_sudo(client: Client, provider: GenericProvider):
provider.user("user-1").add()
provider.sudorule("test").add(user="user-1", host="ALL", command="/bin/ls")
client.authselect.select("sssd", ["with-sudo"])
client.sssd.enable_responder("sudo")
client.sssd.start()
assert client.auth.sudo.list(
"user-1", "Secret123", expected=["(root) /bin/ls"]
), "sudo rule was not listed for user-1 with with-sudo enabled!"
assert client.auth.sudo.run(
"user-1", "Secret123", command="/bin/ls /root"
), "sudo command failed for user-1 with with-sudo enabled!"
client.authselect.disable_feature(["with-sudo"])
assert not client.auth.sudo.list(
"user-1", "Secret123", expected=["(root) /bin/ls"]
), "sudo rule should not be listed for user-1 after with-sudo was disabled!"From test_winbind__with_krb5:
import pytest
from authselect_test_framework.roles.client import Client
from authselect_test_framework.roles.samba import Samba
from authselect_test_framework.profiles import Profile
@pytest.mark.topology(Profile.Winbind)
def test_winbind__with_krb5(client: Client, provider: Samba):
provider.user("user-1").add(uid=10002, gid=10002, home="/home/user-1", shell="/bin/bash")
client.authselect.select("winbind", ["with-krb5"])
client.winbind.start()
result = client.host.conn.run(
f'su - "user-1" -c "kinit user-1@{provider.realm} && klist"',
input="Secret123",
raise_on_error=False,
)
assert result.rc == 0, "kinit should succeed with 'with-krb5' enabled!"
assert f"krbtgt/{provider.realm}@{provider.realm}" in result.stdout, (
"klist should show a TGT!"
)
client.authselect.disable_feature(["with-krb5"])
result = client.host.conn.run(
f'su - "user-1" -c "kinit user-1@{provider.realm} && klist"',
input="Secret123",
raise_on_error=False,
)
assert result.rc == 0, "kinit should still succeed after 'with-krb5' was disabled!"
assert client.auth.ssh.password("user-1", password="Secret123"), (
"domain user password login should succeed after 'with-krb5' was disabled!"
)Authselect tests load the framework through conftest.py:
from pytest_mh import MultihostPlugin
from authselect_test_framework.config import AuthselectMultihostConfig
pytest_plugins = (
"pytest_importance",
"pytest_mh",
"pytest_ticket",
"pytest_tier",
"authselect_test_framework.fixtures",
"authselect_test_framework.markers",
)
def pytest_plugin_registered(plugin) -> None:
if isinstance(plugin, MultihostPlugin):
plugin.config_class = AuthselectMultihostConfig.. seealso::
``src/tests/system/tests/test_profiles.py`` for shared feature tests across
all profiles and provider-backed topologies.
``src/tests/system/tests/test_local.py`` for local-only PAM feature tests.
:doc:`guides/test-sssd-profile` for SSSD-only examples from ``test_sssd.py``.
:doc:`guides/test-winbind-profile` for Winbind-only examples from ``test_winbind.py``.
:doc:`api` for the full API reference.