Files
ModelForge-Public/backend/tests/test_secret_slots_persistence.py
T

102 lines
4.8 KiB
Python

# ruff: noqa: S106 - isolated synthetic persistence fixtures
"""Secret history fingerprint and migration contracts without external credentials."""
import copy
import importlib.util
from datetime import UTC, datetime
from pathlib import Path
import pytest
from alembic.migration import MigrationContext
from alembic.operations import Operations
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from modelforge_api.persistence.models import Base, SecretSlotState, SecretSlotVersion
from modelforge_api.services.audit import AuditWriter
from modelforge_api.services.recovery_fingerprint import (
SECRET_SCHEMA,
SECRET_TABLES,
FingerprintCompatibility,
_without_creative,
_without_lab_execution,
_without_model_changes,
_without_model_updates,
_without_secrets,
_without_selected_comparisons,
audit_fingerprint_differences_are_compatible,
fingerprint_compatibility,
unclassified_tables,
)
from modelforge_api.services.recovery_fingerprint import (
fingerprint_session as _current_fingerprint_session,
)
def fingerprint_session(session: Session):
return _without_lab_execution(_without_model_changes(_current_fingerprint_session(session)))
def test_secret_history_is_strict_except_verified_recovery_marker() -> None:
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
with Session(engine) as session:
assert not unclassified_tables()
session.add(SecretSlotVersion(version=1, key_id="qualification", secret_ciphertext="opaque",
secret_nonce="nonce", created_at=datetime.now(UTC)))
session.commit()
before = _without_creative(_without_model_updates(_without_selected_comparisons(fingerprint_session(session))))
assert before["tables"]["platform_secret_versions"]["redacted_columns"] == ["secret_ciphertext", "secret_nonce"]
AuditWriter(session, "operator", "qualification").write("RECOVERY_RECONCILIATION_COMPLETED", "restore", None, {"restored": True})
session.commit()
after = _without_creative(_without_model_updates(_without_selected_comparisons(fingerprint_session(session))))
contract = fingerprint_compatibility(before, after, source_schema_revision=SECRET_SCHEMA)
assert contract.mode == "ALERT_NOTIFICATION_UPGRADE|PORTFOLIO_UPGRADE|SECRET_STRICT"
assert audit_fingerprint_differences_are_compatible(before, after, contract)
unrelated_marker = FingerprintCompatibility(
True,
f"UNRELATED|{contract.mode}",
contract.allow_audit_checkpoint_introduction,
contract.reason,
)
assert not audit_fingerprint_differences_are_compatible(before, after, unrelated_marker)
for table in SECRET_TABLES:
altered = copy.deepcopy(after)
altered["tables"][table]["digest"] = "0" * 64
assert not audit_fingerprint_differences_are_compatible(before, altered, contract)
engine.dispose()
def test_prior_schema_requires_exact_empty_secret_introduction() -> None:
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
with Session(engine) as session:
after = _without_creative(_without_model_updates(_without_selected_comparisons(fingerprint_session(session))))
before = _without_secrets(after)
contract = fingerprint_compatibility(before, after, source_schema_revision="20260908_0027")
assert contract.compatible
assert audit_fingerprint_differences_are_compatible(before, after, contract)
for table in SECRET_TABLES:
altered = copy.deepcopy(after)
altered["tables"][table]["digest"] = "0" * 64
assert not fingerprint_compatibility(before, altered, source_schema_revision="20260908_0027").compatible
engine.dispose()
def test_secret_migration_seed_empty_downgrade_and_history_refusal() -> None:
path = Path(__file__).resolve().parents[1] / "alembic/versions/20260908_0028_platform_secret_slots.py"
spec = importlib.util.spec_from_file_location("secret_migration", path)
assert spec and spec.loader
migration = importlib.util.module_from_spec(spec)
spec.loader.exec_module(migration)
engine = create_engine("sqlite://")
with engine.begin() as connection, Operations.context(MigrationContext.configure(connection)):
migration.upgrade()
assert connection.scalar(select(SecretSlotState.revision)) == 0
migration.downgrade()
migration.upgrade()
connection.execute(SecretSlotVersion.__table__.insert().values(version=1, key_id="qualification",
secret_ciphertext="opaque", secret_nonce="nonce", created_at=datetime.now(UTC)))
with pytest.raises(RuntimeError, match="history exists"):
migration.downgrade()
engine.dispose()