from unittest.mock import AsyncMock import pytest from ingestor.connection.certs import CertProvider @pytest.mark.asyncio async def test_fetch_returns_local_paths(tmp_path): mongo = AsyncMock() mongo.find_one.return_value = { 'client_cert': {'minio_object_name': 'platform/certificates/client_cert_c.pem'}, 'private_key': {'minio_object_name': 'platform/certificates/private_key_k.pem'}, } minio = AsyncMock() minio.download_file.side_effect = [b'cert-bytes', b'key-bytes'] provider = CertProvider(mongo, minio, cache_dir=str(tmp_path), bucket='sientia-dataops') cert, key = await provider.fetch() assert cert.endswith('c.pem') and key.endswith('k.pem') assert minio.download_file.await_count == 2 mongo.find_one.assert_awaited_once_with('platform_certificates', {}, {}) minio.download_file.assert_any_await( 'platform/certificates/client_cert_c.pem', 'sientia-dataops', {} ) assert (tmp_path / 'client_cert_c.pem').read_bytes() == b'cert-bytes' assert (tmp_path / 'private_key_k.pem').read_bytes() == b'key-bytes' @pytest.mark.asyncio async def test_lazy_minio_builds_client_once_on_first_download(): from unittest.mock import MagicMock from ingestor.connection.certs import LazyMinio client = AsyncMock() client.download_file.return_value = b'bytes' factory = MagicMock(return_value=client) minio = LazyMinio(factory) factory.assert_not_called() # no network at construction assert await minio.download_file('a.pem', 'bucket', {}) == b'bytes' await minio.download_file('b.pem', 'bucket', {}) factory.assert_called_once() assert client.download_file.await_count == 2 def test_build_cert_provider_is_lazy_and_uses_config(monkeypatch): from unittest.mock import MagicMock from ingestor.config import GatewayConfig from ingestor.connection import certs minio_cls = MagicMock() monkeypatch.setattr(certs, 'MinioRepository', minio_cls) mongo = MagicMock() provider = certs.build_cert_provider( mongo=mongo, config=GatewayConfig(cert_bucket='streamlit-connectors', cert_cache_dir='certs-cache'), logger=MagicMock(), notification_handler=MagicMock(), metrics_controller=MagicMock(), ) assert provider.mongo is mongo assert provider.bucket == 'streamlit-connectors' assert provider.cache_dir == 'certs-cache' assert isinstance(provider.minio, certs.LazyMinio) minio_cls.assert_not_called() # MinIO is only reached when a cert is needed def _provider(tmp_path, doc=None, data=b'pem'): from unittest.mock import MagicMock mongo = MagicMock() mongo.find_one = AsyncMock( return_value=doc if doc is not None else { 'client_cert': {'minio_object_name': 'platform_certificates/c.pem'}, 'private_key': {'minio_object_name': 'platform_certificates/k.pem'}, } ) minio = MagicMock() minio.download_file = AsyncMock(return_value=data) return CertProvider(mongo=mongo, minio=minio, cache_dir=str(tmp_path), bucket='b'), minio @pytest.mark.asyncio @pytest.mark.parametrize( 'doc', [ None, {}, {'client_cert': {'minio_object_name': 'c.pem'}}, {'client_cert': None, 'private_key': {}}, ], ) async def test_fetch_without_platform_certificate_raises_clear_error(tmp_path, doc): from ingestor.connection.certs import CertificateNotConfiguredError provider, minio = _provider(tmp_path) provider.mongo.find_one.return_value = doc with pytest.raises(CertificateNotConfiguredError, match='platform_certificates'): await provider.fetch() minio.download_file.assert_not_awaited() @pytest.mark.asyncio async def test_fetch_is_cached_until_invalidated(tmp_path): provider, minio = _provider(tmp_path) first = await provider.fetch() second = await provider.fetch() assert first == second assert minio.download_file.await_count == 2 # cert + key, once provider.invalidate() await provider.fetch() assert minio.download_file.await_count == 4 @pytest.mark.asyncio async def test_concurrent_fetches_download_once(tmp_path): import asyncio provider, minio = _provider(tmp_path) results = await asyncio.gather(*(provider.fetch() for _ in range(5))) assert len(set(results)) == 1 assert minio.download_file.await_count == 2 @pytest.mark.asyncio async def test_download_replaces_file_atomically(tmp_path): provider, minio = _provider(tmp_path, data=b'new') (tmp_path / 'c.pem').write_bytes(b'old') cert_path, _ = await provider.fetch() assert (tmp_path / 'c.pem').read_bytes() == b'new' assert cert_path == str(tmp_path / 'c.pem') assert not list(tmp_path.glob('*.tmp'))