120 lines
3.6 KiB
Python
120 lines
3.6 KiB
Python
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()
|