mirror of
https://github.com/apache/superset.git
synced 2026-08-12 11:11:01 +00:00
refactor: make import/expression layer SQLAlchemy 2.0-compatible (#41179)
Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Code
parent
11a4a17dff
commit
adc03ce525
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user