Files
college-infos/tests/unit/test_xmpp_settings.py

302 lines
12 KiB
Python

"""Tests unitaires pour les contraintes de sécurité et validateurs de XmppSettings.
Ce module valide les contraintes de sécurité et les validateurs qui seront ajoutés
à la classe XmppSettings dans pronote_sync/config/settings.py.
Les tests doivent être initialement en échec (RED) car les contraintes et validateurs
n'existent pas encore dans le code de production.
"""
import pytest
from pydantic import SecretStr, ValidationError
from pronote_sync.config.settings import XmppSettings
class TestPortConstraints:
"""Tests des contraintes sur le champ port."""
def test_port_below_1_rejected(self) -> None:
"""Vérifie que port < 1 est rejeté.
:raises ValidationError: Si le port est inférieur à 1.
"""
with pytest.raises(ValidationError) as exc_info:
XmppSettings(port=0)
assert "port" in str(exc_info.value).lower()
def test_port_above_65535_rejected(self) -> None:
"""Vérifie que port > 65535 est rejeté.
:raises ValidationError: Si le port est supérieur à 65535.
"""
with pytest.raises(ValidationError) as exc_info:
XmppSettings(port=70000)
assert "port" in str(exc_info.value).lower()
def test_port_default_5222(self) -> None:
"""Vérifie que la valeur par défaut de port est 5222.
:return: Vérifie que XmppSettings().port == 5222.
:rtype: None
"""
settings = XmppSettings()
assert settings.port == 5222
def test_port_valid(self) -> None:
"""Vérifie que les ports valides sont acceptés.
:return: Vérifie que XmppSettings(port=5222) et XmppSettings(port=5223) sont valides.
:rtype: None
"""
settings1 = XmppSettings(port=5222)
assert settings1.port == 5222
settings2 = XmppSettings(port=5223)
assert settings2.port == 5223
class TestTimeoutConstraints:
"""Tests des contraintes sur le champ timeout."""
def test_timeout_zero_rejected(self) -> None:
"""Vérifie que timeout = 0 est rejeté.
:raises ValidationError: Si le timeout est égal à 0.
"""
with pytest.raises(ValidationError) as exc_info:
XmppSettings(timeout=0)
assert "timeout" in str(exc_info.value).lower()
def test_timeout_negative_rejected(self) -> None:
"""Vérifie que timeout < 0 est rejeté.
:raises ValidationError: Si le timeout est négatif.
"""
with pytest.raises(ValidationError) as exc_info:
XmppSettings(timeout=-1)
assert "timeout" in str(exc_info.value).lower()
def test_timeout_default_30(self) -> None:
"""Vérifie que la valeur par défaut de timeout est 30.
:return: Vérifie que XmppSettings().timeout == 30.
:rtype: None
"""
settings = XmppSettings()
assert settings.timeout == 30
def test_timeout_positive_valid(self) -> None:
"""Vérifie que les valeurs positives de timeout sont acceptées.
:return: Vérifie que XmppSettings(timeout=10) est valide.
:rtype: None
"""
settings = XmppSettings(timeout=10)
assert settings.timeout == 10
class TestTlsPolicy:
"""Tests de la politique TLS pour le champ use_tls."""
def test_tls_disabled_with_remote_host_rejected(self) -> None:
"""Vérifie que tls_mode="disabled" avec un hôte distant est rejeté.
:raises ValidationError: Si tls_mode="disabled" et host n'est pas une boucle locale.
"""
with pytest.raises(ValidationError) as exc_info:
XmppSettings(tls_mode="disabled", host="talk.example.com")
assert "tls" in str(exc_info.value).lower()
def test_tls_disabled_with_remote_host_rejected_when_enabled(self) -> None:
"""Vérifie que la politique TLS s'applique même quand le canal est activé."""
with pytest.raises(ValidationError):
XmppSettings(enabled=True, tls_mode="disabled", host="talk.example.com")
def test_use_tls_false_maps_to_starttls(self) -> None:
"""Vérifie que l'alias obsolète use_tls=False mappe vers tls_mode='starttls'.
:return: Vérifie le mapping et l'émission d'un DeprecationWarning.
:rtype: None
"""
with pytest.warns(DeprecationWarning):
settings = XmppSettings(use_tls=False, host="talk.example.com")
assert settings.tls_mode == "starttls"
assert settings.host == "talk.example.com"
def test_use_tls_true_maps_to_direct(self) -> None:
"""Vérifie que l'alias obsolète use_tls=True mappe vers tls_mode='direct'.
:return: Vérifie le mapping et l'émission d'un DeprecationWarning.
:rtype: None
"""
with pytest.warns(DeprecationWarning):
settings = XmppSettings(use_tls=True, host="talk.example.com")
assert settings.tls_mode == "direct"
assert settings.host == "talk.example.com"
def test_default_tls_mode_is_starttls(self) -> None:
"""Vérifie que tls_mode vaut 'starttls' par défaut avec les timeouts par défaut.
:return: Vérifie tls_mode='starttls', connect_timeout=15, cleanup_timeout=10.
:rtype: None
"""
settings = XmppSettings()
assert settings.tls_mode == "starttls"
assert settings.connect_timeout == 15
assert settings.cleanup_timeout == 10
def test_use_tls_false_with_localhost_allowed(self) -> None:
"""Vérifie que use_tls=False avec localhost est autorisé.
:return: Vérifie que XmppSettings(use_tls=False, host="localhost") est valide.
:rtype: None
"""
settings = XmppSettings(use_tls=False, host="localhost")
assert settings.tls_mode == "starttls"
assert settings.host == "localhost"
def test_use_tls_false_with_127_allowed(self) -> None:
"""Vérifie que use_tls=False avec 127.0.0.1 est autorisé.
:return: Vérifie que XmppSettings(use_tls=False, host="127.0.0.1") est valide.
:rtype: None
"""
settings = XmppSettings(use_tls=False, host="127.0.0.1")
assert settings.tls_mode == "starttls"
assert settings.host == "127.0.0.1"
def test_use_tls_false_with_ipv6_loopback_allowed(self) -> None:
"""Vérifie que use_tls=False avec ::1 est autorisé.
:return: Vérifie que XmppSettings(use_tls=False, host="::1") est valide.
:rtype: None
"""
settings = XmppSettings(use_tls=False, host="::1")
assert settings.tls_mode == "starttls"
assert settings.host == "::1"
def test_use_tls_true_with_remote_host_allowed(self) -> None:
"""Vérifie que use_tls=True avec un hôte distant est autorisé.
:return: Vérifie que XmppSettings(use_tls=True, host="talk.example.com") est valide.
:rtype: None
"""
settings = XmppSettings(use_tls=True, host="talk.example.com")
assert settings.tls_mode == "direct"
assert settings.host == "talk.example.com"
def test_use_tls_true_with_empty_host_allowed(self) -> None:
"""Vérifie que use_tls=True avec host vide est autorisé.
:return: Vérifie que XmppSettings(use_tls=True, host="") est valide.
:rtype: None
"""
settings = XmppSettings(use_tls=True, host="")
assert settings.tls_mode == "direct"
assert settings.host == ""
class TestNoSecretInErrorMessages:
"""Tests de sécurité : vérifie que les messages d'erreur ne contiennent pas de secrets."""
def test_no_secret_in_validation_error(self) -> None:
"""Vérifie que les messages de ValidationError ne contiennent pas de secrets.
Crée une instance avec des valeurs sensibles et vérifie que l'erreur de validation
ne contient pas ces valeurs dans son message.
:raises ValidationError: Si tls_mode="disabled" avec un hôte non-local.
:return: Vérifie que le message d'erreur ne contient pas les secrets.
:rtype: None
"""
# Utilisation de valeurs sentinelles pour éviter toute fuite
sentinel_jid = "test_jid@example.com"
sentinel_password = SecretStr("test_password_123")
sentinel_to = "test_to@example.com"
with pytest.raises(ValidationError) as exc_info:
XmppSettings(
tls_mode="disabled",
host="talk.example.com",
jid=sentinel_jid,
password=sentinel_password,
to=sentinel_to,
)
error_message = str(exc_info.value).lower()
# Vérifie que les valeurs sensibles ne sont pas dans le message d'erreur
assert "test_jid@example.com" not in error_message
assert "test_password_123" not in error_message
assert "test_to@example.com" not in error_message
assert "secret" not in error_message
class TestXmppSettingsTlsMode:
"""Tests supplémentaires pour les validateurs de XmppSettings."""
def test_use_tls_true_maps_to_direct_with_warning(self) -> None:
"""Vérifie que use_tls=True mappe vers tls_mode='direct' avec DeprecationWarning.
:return: Vérifie le mapping et l'émission d'un DeprecationWarning.
:rtype: None
"""
with pytest.warns(DeprecationWarning):
settings = XmppSettings(use_tls=True, host="talk.example.com")
assert settings.tls_mode == "direct"
def test_use_tls_false_maps_to_starttls_with_warning(self) -> None:
"""Vérifie que use_tls=False mappe vers tls_mode='starttls' avec DeprecationWarning.
:return: Vérifie le mapping et l'émission d'un DeprecationWarning.
:rtype: None
"""
with pytest.warns(DeprecationWarning):
settings = XmppSettings(use_tls=False, host="talk.example.com")
assert settings.tls_mode == "starttls"
def test_tls_mode_disabled_rejected_for_remote(self) -> None:
"""Vérifie que tls_mode='disabled' avec un hôte distant est rejeté.
:raises ValidationError: Si tls_mode="disabled" et host n'est pas une boucle locale.
"""
with pytest.raises(ValidationError) as exc_info:
XmppSettings(tls_mode="disabled", host="example.com")
assert "tls" in str(exc_info.value).lower()
def test_tls_mode_disabled_allowed_for_loopback(self) -> None:
"""Vérifie que tls_mode='disabled' avec host='127.0.0.1' est valide.
:return: Vérifie que XmppSettings(tls_mode='disabled', host='127.0.0.1') est valide.
:rtype: None
"""
settings = XmppSettings(tls_mode="disabled", host="127.0.0.1")
assert settings.tls_mode == "disabled"
assert settings.host == "127.0.0.1"
def test_connect_timeout_must_be_positive(self) -> None:
"""Vérifie que connect_timeout doit être strictement positif.
:raises ValidationError: Si connect_timeout <= 0.
"""
with pytest.raises(ValidationError) as exc_info:
XmppSettings(connect_timeout=0)
assert "connect_timeout" in str(exc_info.value).lower()
with pytest.raises(ValidationError) as exc_info:
XmppSettings(connect_timeout=-1)
assert "connect_timeout" in str(exc_info.value).lower()
def test_cleanup_timeout_must_be_positive(self) -> None:
"""Vérifie que cleanup_timeout doit être strictement positif.
:raises ValidationError: Si cleanup_timeout <= 0.
"""
with pytest.raises(ValidationError) as exc_info:
XmppSettings(cleanup_timeout=0)
assert "cleanup_timeout" in str(exc_info.value).lower()
with pytest.raises(ValidationError) as exc_info:
XmppSettings(cleanup_timeout=-1)
assert "cleanup_timeout" in str(exc_info.value).lower()