import socket import time import pytest from requests import RequestException, Session from nucypher.utilities.certs import ( Address, CertificateCache, P2PSession, SelfSignedCertificateAdapter, ) # Define test URLs VALID_URL = "https://lynx.nucypher.network:9151/status" INVALID_URL = "https://nonexistent-domain.com" MOCK_CERT = """-----BEGIN CERTIFICATE----- MIIDXTCCAkWgAwIBAgIJALm157+YvLEhMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV ... -----END CERTIFICATE-----""" VALID_BUT_INCORRECT_CERT_FOR_VALID_URL = """-----BEGIN CERTIFICATE----- MIIBgzCCAQigAwIBAgIUYZMjb9wgSIv0G3H9zP6Xezi3y6kwCgYIKoZIzj0EAwQw GDEWMBQGA1UEAwwNMTg4LjE2Ni4yNy40NjAeFw0yNDAxMzAxMjQ0MzhaFw0yNTAx MjkxMjQ0MzhaMBgxFjAUBgNVBAMMDTE4OC4xNjYuMjcuNDYwdjAQBgcqhkjOPQIB BgUrgQQAIgNiAASnd+YYrbrV3WW/hb1+4+RRD/lWLkcgKM5JjZLjuwNU/Ndr1vEl qOAwbz+fcdwgJ7SAkSoK2fQOt90NnnBPDA12MCc0ScwyiQxS7Cm382B4h3No4M4Z E3bLLn1u69g9Y26jEzARMA8GA1UdEQQIMAaHBLymGy4wCgYIKoZIzj0EAwQDaQAw ZgIxAL4cpbec9Hs8O4uXB8zESJJ32err5jejFhWOFexppRTNjhM5copO9c8x24zJ IzqeQgIxALCe9ynrDkT/tOtBNjvPiNvR8aosRsgdsQCcbk3fUCsYXSXTuphpDgMf IKaHuG9nuw== -----END CERTIFICATE----- """ @pytest.fixture def cache(): return CertificateCache() @pytest.fixture def adapter(cache): _adapter = SelfSignedCertificateAdapter(certificate_cache=cache) return _adapter @pytest.fixture def session(adapter): s = P2PSession() s.adapter = adapter # Use the same adapter instance return s def test_init_adapter(cache, adapter): assert isinstance(adapter, SelfSignedCertificateAdapter) def test_cert_cache_set_get(): cache = CertificateCache() address = Address("example.com", 443) cache.set(address, MOCK_CERT) assert cache.get(address) == MOCK_CERT def test_cert_cache_expiry(): cache = CertificateCache(cache_duration=1) address = Address("example.com", 443) cache.set(address, MOCK_CERT) assert not cache.is_expired(address) # Wait for the cert to expire time.sleep(2) assert cache.is_expired(address) def test_cache_cert(cache): address = Address("example.com", 443) cache.set(address, "cert_data") assert cache.get(address) == "cert_data" def test_send_request(session, mocker): mocked_refresh = mocker.patch.object( session, "_refresh_certificate", return_value=MOCK_CERT ) mocker.patch.object(Session, "send", return_value="response") response = session.send(mocker.Mock(url=VALID_URL)) mocked_refresh.assert_called() assert response == "response" @pytest.mark.xfail(reason="This test uses a real network connection") def test_https_request_with_cert_caching(): # Create a session with certificate caching session = P2PSession() # Send a request (it should succeed) response = session.get(VALID_URL) assert response.status_code == 200 # Send another request to the same URL (it should use the cached certificate) response = session.get(VALID_URL) assert response.status_code == 200 @pytest.mark.xfail(reason="This test uses a real network connection") def test_https_request_with_cert_refresh(): # Create a session with certificate caching session = P2PSession() # Send a request (it should succeed) response = session.get(VALID_URL) assert response.status_code == 200 # Manually expire the cached certificate hostname, port = P2PSession._resolve_address(VALID_URL) session.certificate_cache._expirations[(hostname, port)] = 0 # Send another request to the same URL (it should refresh the certificate) response = session.get(VALID_URL) assert response.status_code == 200 def test_https_request_with_invalid_cached_cert_and_refresh(): # Create a session with certificate caching session = P2PSession() hostname, port = P2PSession._resolve_address(VALID_URL) session.certificate_cache.set( Address(hostname, port), VALID_BUT_INCORRECT_CERT_FOR_VALID_URL ) # Send a request (it should succeed after retrying and refreshing cert) response = session.get(VALID_URL) assert response.status_code == 200 def test_fetch_server_cert_socket_error(session, mocker): mocker.patch("socket.create_connection", side_effect=socket.error) address = Address("localhost", 443) with pytest.raises(socket.error): session._refresh_certificate(address) def test_send_request_exception(session, mocker): """Test that a RequestException is raised when the request fails.""" mock_request = mocker.Mock() mock_request.url = "https://localhost" mocker.patch.object(session, "_refresh_certificate", return_value=MOCK_CERT) mocker.patch("requests.Session.send", side_effect=RequestException) with pytest.raises(RequestException): session.send(mock_request) def test_retry_on_request_exception(session, mocker): """Test to ensure that the request is retried upon encountering a RequestException.""" mock_request = mocker.Mock() mock_request.url = "https://localhost" mocker.patch.object(session, "_refresh_certificate", return_value=MOCK_CERT) mocker.patch("requests.Session.send", side_effect=[RequestException, "response"]) response = session.send(mock_request) assert response == "response"