"""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_use_tls_false_with_remote_host_rejected(self) -> None: """Vérifie que use_tls=False avec un hôte distant est rejeté. :raises ValidationError: Si use_tls=False et host n'est pas une boucle locale. """ with pytest.raises(ValidationError) as exc_info: XmppSettings(use_tls=False, host="talk.example.com") assert "use_tls" in str(exc_info.value).lower() or "tls" in str(exc_info.value).lower() def test_use_tls_false_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, use_tls=False, host="talk.example.com") 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.use_tls is False 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.use_tls is False 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.use_tls is False 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.use_tls is True 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.use_tls is True 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 use_tls=False 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( use_tls=False, 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