From 80b697097013be5841b7d514e334ee36bac4dde5 Mon Sep 17 00:00:00 2001 From: Jiang Date: Wed, 25 Feb 2026 16:54:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E5=A4=84=E7=90=86=E7=9A=84=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_metadata_repository_dsn_decrypt.py | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/unit/test_metadata_repository_dsn_decrypt.py diff --git a/tests/unit/test_metadata_repository_dsn_decrypt.py b/tests/unit/test_metadata_repository_dsn_decrypt.py new file mode 100644 index 0000000..02796e1 --- /dev/null +++ b/tests/unit/test_metadata_repository_dsn_decrypt.py @@ -0,0 +1,119 @@ +import asyncio +from types import SimpleNamespace +from unittest.mock import AsyncMock +from uuid import uuid4 + +import pytest +from cryptography.fernet import InvalidToken + +from app.infra.repositories.metadata_repository import MetadataRepository + + +class _DummyResult: + def __init__(self, record): + self._record = record + + def scalar_one_or_none(self): + return self._record + + +class _DummyEncryptor: + def __init__(self, decrypted=None, raise_invalid_token=False): + self._decrypted = decrypted + self._raise_invalid_token = raise_invalid_token + self.encrypted_values = [] + + def decrypt(self, _value): + if self._raise_invalid_token: + raise InvalidToken() + return self._decrypted + +def _build_record(dsn_encrypted: str): + return SimpleNamespace( + project_id=uuid4(), + db_role="biz_data", + db_type="postgresql", + dsn_encrypted=dsn_encrypted, + pool_min_size=1, + pool_max_size=5, + ) + + +def test_invalid_token_with_plaintext_dsn_value_raises_clear_error(monkeypatch): + record = _build_record("postgresql://user:p@ss@localhost:5432/db") + session = SimpleNamespace( + execute=None, + commit=None, + ) + session.execute = AsyncMock(return_value=_DummyResult(record)) + session.commit = AsyncMock() + encryptor = _DummyEncryptor(raise_invalid_token=True) + repo = MetadataRepository(session) + + monkeypatch.setattr( + "app.infra.repositories.metadata_repository.is_database_encryption_configured", + lambda: True, + ) + monkeypatch.setattr( + "app.infra.repositories.metadata_repository.get_database_encryptor", + lambda: encryptor, + ) + + with pytest.raises( + ValueError, + match="DATABASE_ENCRYPTION_KEY mismatch or invalid dsn_encrypted value", + ): + asyncio.run(repo.get_project_db_routing(record.project_id, "biz_data")) + session.commit.assert_not_awaited() + + +def test_invalid_token_with_non_dsn_value_raises_clear_error(monkeypatch): + record = _build_record("gAAAAABinvalidciphertext") + session = SimpleNamespace( + execute=None, + commit=None, + ) + session.execute = AsyncMock(return_value=_DummyResult(record)) + session.commit = AsyncMock() + repo = MetadataRepository(session) + + monkeypatch.setattr( + "app.infra.repositories.metadata_repository.is_database_encryption_configured", + lambda: True, + ) + monkeypatch.setattr( + "app.infra.repositories.metadata_repository.get_database_encryptor", + lambda: _DummyEncryptor(raise_invalid_token=True), + ) + + with pytest.raises( + ValueError, + match="DATABASE_ENCRYPTION_KEY mismatch or invalid dsn_encrypted value", + ): + asyncio.run(repo.get_project_db_routing(record.project_id, "biz_data")) + session.commit.assert_not_awaited() + + +def test_encrypted_dsn_decrypts_without_migration(monkeypatch): + record = _build_record("encrypted-value") + session = SimpleNamespace( + execute=None, + commit=None, + ) + session.execute = AsyncMock(return_value=_DummyResult(record)) + session.commit = AsyncMock() + repo = MetadataRepository(session) + + monkeypatch.setattr( + "app.infra.repositories.metadata_repository.is_database_encryption_configured", + lambda: True, + ) + monkeypatch.setattr( + "app.infra.repositories.metadata_repository.get_database_encryptor", + lambda: _DummyEncryptor(decrypted="postgresql://u:p@ss@host/db"), + ) + + routing = asyncio.run(repo.get_project_db_routing(record.project_id, "biz_data")) + + assert routing.dsn == "postgresql://u:p%40ss@host/db" + session.commit.assert_not_awaited()