Files
ping-mee-self-service-platform/portal/tests/test_directory.py
ebbe.bassandClaude Sonnet 5.5 b87ffae94c feat: read the lockout threshold and window in the password policy
The password change must stay below the domain's lockout threshold. The policy now carries lockout_threshold and lockout_window, from the domain or from the fine-grained policy that applies. AD-2's read allow-list names the new attributes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
2026-10-18 09:55:10 +02:00

112 lines
3.8 KiB
Python

"""Contract tests for the in-memory directory that the other tickets build against."""
from datetime import UTC, datetime, timedelta
import pytest
from portal.adapters.directory import (
WRITABLE_ATTRIBUTES,
DirectoryUnavailable,
FakeDirectory,
Forbidden,
PasswordRejected,
ValidationFailed,
)
@pytest.fixture
def directory():
d = FakeDirectory()
d.add_user("Alice", given_name="Alice", mail="alice@example.test", is_publisher=True)
d.add_user("gone", enabled=False)
d.add_user("locked", locked=True)
d.add_user("expired", password_last_set=None)
return d
def test_find_user_is_case_insensitive_and_keeps_the_exact_name(directory):
assert directory.find_user("alice").sam == "Alice"
assert directory.find_user("nobody") is None
assert directory.find_user("") is None
def test_account_state_is_reported(directory):
assert directory.find_user("gone").enabled is False
assert directory.find_user("locked").locked is True
assert directory.find_user("expired").must_change_password is True
assert directory.find_user("Alice").is_publisher is True
assert directory.find_user("Alice").is_admin is False
def test_verify_password(directory):
assert directory.verify_password("alice", "Correct-Horse-9") is True
assert directory.verify_password("alice", "wrong") is False
assert directory.verify_password("nobody", "Correct-Horse-9") is False
def test_an_empty_password_is_never_valid(directory):
directory.add_user("blank", password="")
assert directory.verify_password("blank", "") is False
def test_allowed_attributes_can_be_written_and_cleared(directory):
directory.update_attributes("alice", {"mobile": "+49 1", "givenName": "Al"})
user = directory.find_user("alice")
assert (user.mobile, user.given_name) == ("+49 1", "Al")
directory.update_attributes("alice", {"mobile": None})
assert directory.find_user("alice").mobile == ""
@pytest.mark.parametrize(
"name", ["description", "memberOf", "unicodePwd", "sAMAccountName", "userAccountControl"]
)
def test_other_attributes_are_refused_before_anything_is_written(directory, name):
with pytest.raises(Forbidden):
directory.update_attributes("alice", {"givenName": "Changed", name: "x"})
assert directory.find_user("alice").given_name == "Alice"
def test_writing_an_unknown_user_fails(directory):
with pytest.raises(ValidationFailed):
directory.update_attributes("nobody", {"mobile": "1"})
def test_writable_list_is_exactly_the_agreed_six():
assert set(WRITABLE_ATTRIBUTES) == {
"givenName",
"sn",
"displayName",
"mail",
"telephoneNumber",
"mobile",
}
def test_password_policy_is_returned(directory):
policy = directory.password_policy("alice")
assert policy.min_length == 8
assert policy.min_age == timedelta(0)
assert policy.lockout_threshold == 5
def test_password_change_needs_the_current_password(directory):
with pytest.raises(PasswordRejected) as exc:
directory.change_password("alice", "wrong", "Another-Pass-7!")
assert exc.value.reason == "wrong_current"
directory.change_password("alice", "Correct-Horse-9", "Another-Pass-7!")
assert directory.verify_password("alice", "Another-Pass-7!") is True
assert directory.find_user("alice").password_last_set > datetime.now(UTC) - timedelta(minutes=1)
@pytest.mark.parametrize("new", ["short1A", "alllowercase-long", "Correct-Horse-9"])
def test_policy_violations_are_reported_as_policy(directory, new):
with pytest.raises(PasswordRejected) as exc:
directory.change_password("alice", "Correct-Horse-9", new)
assert exc.value.reason == "policy"
def test_an_outage_is_a_typed_error(directory):
directory.unavailable = True
with pytest.raises(DirectoryUnavailable):
directory.find_user("alice")