refactor: make import/expression layer SQLAlchemy 2.0-compatible (#41179)

Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
Evan Rusackas
2026-06-29 11:38:59 -07:00
committed by GitHub
co-authored by Claude Code
parent 11a4a17dff
commit adc03ce525
14 changed files with 112 additions and 46 deletions
+25 -13
View File
@@ -57,6 +57,18 @@ def _engine_migrator(target_engine: type) -> SecretsMigrator:
return migrator
class _Row:
"""Minimal stand-in for a SQLAlchemy ``Row``.
``_re_encrypt_row`` accesses columns via ``row._mapping[...]`` (the
SQLAlchemy 2.0-compatible idiom), so the fixtures wrap their column dicts
in an object exposing that attribute rather than passing a bare ``dict``.
"""
def __init__(self, mapping: dict[str, object]) -> None:
self._mapping = mapping
def test_default_engine_is_aes_cbc() -> None:
"""Without config, the adapter keeps the historical AES-CBC engine."""
field = SQLAlchemyUtilsAdapter().create(SECRET, String(128))
@@ -156,7 +168,7 @@ def test_engine_migration_cbc_to_gcm_re_encrypts() -> None:
migrator = _engine_migrator(AesGcmEngine)
conn = MagicMock()
row = {"id": 1, "password": ciphertext}
row = _Row({"id": 1, "password": ciphertext})
stats = ReEncryptStats()
migrator._re_encrypt_row( # noqa: SLF001
@@ -165,7 +177,7 @@ def test_engine_migration_cbc_to_gcm_re_encrypts() -> None:
assert stats == ReEncryptStats(re_encrypted=1)
assert conn.execute.call_count == 1
new_value = conn.execute.call_args.kwargs["password"]
new_value = conn.execute.call_args.args[1]["password"]
# The stored value changed and now decrypts as GCM back to the plaintext.
assert new_value != ciphertext
gcm = _encrypted_type(AesGcmEngine)
@@ -183,7 +195,7 @@ def test_engine_migration_idempotent_for_already_target() -> None:
migrator = _engine_migrator(AesGcmEngine)
conn = MagicMock()
row = {"id": 1, "password": gcm_value}
row = _Row({"id": 1, "password": gcm_value})
stats = ReEncryptStats()
migrator._re_encrypt_row( # noqa: SLF001
@@ -206,7 +218,7 @@ def test_engine_migration_reads_cbc_after_config_already_flipped() -> None:
migrator = _engine_migrator(AesGcmEngine)
conn = MagicMock()
row = {"id": 1, "password": cbc_value}
row = _Row({"id": 1, "password": cbc_value})
stats = ReEncryptStats()
migrator._re_encrypt_row( # noqa: SLF001
@@ -214,7 +226,7 @@ def test_engine_migration_reads_cbc_after_config_already_flipped() -> None:
)
assert stats == ReEncryptStats(re_encrypted=1)
new_value = conn.execute.call_args.kwargs["password"]
new_value = conn.execute.call_args.args[1]["password"]
assert gcm_column.process_result_value(new_value, DIALECT) == "hunter2"
@@ -231,7 +243,7 @@ def test_engine_migration_gcm_to_cbc_rolls_back() -> None:
migrator = _engine_migrator(AesEngine)
conn = MagicMock()
row = {"id": 1, "password": gcm_value}
row = _Row({"id": 1, "password": gcm_value})
stats = ReEncryptStats()
migrator._re_encrypt_row( # noqa: SLF001
@@ -239,7 +251,7 @@ def test_engine_migration_gcm_to_cbc_rolls_back() -> None:
)
assert stats == ReEncryptStats(re_encrypted=1)
new_value = conn.execute.call_args.kwargs["password"]
new_value = conn.execute.call_args.args[1]["password"]
assert new_value != gcm_value
# The rolled-back value now decrypts as AES-CBC back to the plaintext.
assert _encrypted_type(AesEngine).process_result_value(new_value, DIALECT) == (
@@ -272,7 +284,7 @@ def test_rollback_authenticated_probe_wins_over_spurious_cbc_skip() -> None:
spurious_target.process_bind_param.return_value = b"new-cbc-ciphertext"
conn = MagicMock()
row = {"id": 1, "password": gcm_value}
row = _Row({"id": 1, "password": gcm_value})
stats = ReEncryptStats()
with mock.patch.object(migrator, "_target_type", return_value=spurious_target):
@@ -302,7 +314,7 @@ def test_combined_key_rotation_and_engine_migration() -> None:
migrator._previous_secret_key = old_key # noqa: SLF001 # rotate key too
conn = MagicMock()
row = {"id": 1, "password": old_value}
row = _Row({"id": 1, "password": old_value})
stats = ReEncryptStats()
migrator._re_encrypt_row( # noqa: SLF001
@@ -310,7 +322,7 @@ def test_combined_key_rotation_and_engine_migration() -> None:
)
assert stats == ReEncryptStats(re_encrypted=1)
new_value = conn.execute.call_args.kwargs["password"]
new_value = conn.execute.call_args.args[1]["password"]
# The migrated value decrypts as GCM under the *current* key.
assert _encrypted_type(AesGcmEngine).process_result_value(new_value, DIALECT) == (
"hunter2"
@@ -346,7 +358,7 @@ def test_key_rotation_for_aes_gcm_column() -> None:
migrator = _key_rotation_migrator(previous_secret_key=old_key)
conn = MagicMock()
row = {"id": 1, "password": old_value}
row = _Row({"id": 1, "password": old_value})
stats = ReEncryptStats()
migrator._re_encrypt_row( # noqa: SLF001
@@ -354,7 +366,7 @@ def test_key_rotation_for_aes_gcm_column() -> None:
)
assert stats == ReEncryptStats(re_encrypted=1)
new_value = conn.execute.call_args.kwargs["password"]
new_value = conn.execute.call_args.args[1]["password"]
assert gcm_column.process_result_value(new_value, DIALECT) == "hunter2"
@@ -362,7 +374,7 @@ def test_engine_migration_unreadable_value_counts_as_failure() -> None:
"""A value no engine/key can read is a failure, not a silent pass-through."""
migrator = _engine_migrator(AesGcmEngine)
conn = MagicMock()
row = {"id": 1, "password": b"not-valid-ciphertext"}
row = _Row({"id": 1, "password": b"not-valid-ciphertext"})
stats = ReEncryptStats()
migrator._re_encrypt_row( # noqa: SLF001