from __future__ import annotations import copy from sqlalchemy import create_engine from sqlalchemy.orm import Session from modelforge_api.domain.configuration import ConfigurationApply, ConfigurationChange from modelforge_api.persistence.models import Base from modelforge_api.services.audit import AuditContext, AuditWriter from modelforge_api.services.configuration_changes import ConfigurationService from modelforge_api.services.recovery_fingerprint import ( CONFIGURATION_TABLES, SELECTED_COMPARISON_SCHEMA, _without_creative, _without_lab_execution, _without_managed_identities, _without_model_changes, _without_model_updates, _without_projects, _without_secrets, _without_selected_comparisons, audit_fingerprint_differences_are_compatible, fingerprint_compatibility, semantic_fingerprint, ) from modelforge_api.settings import Settings def test_upgrade_introduces_only_empty_configuration_and_current_restore_preserves_every_revision() -> None: engine = create_engine("sqlite://") Base.metadata.create_all(engine) with Session(engine) as session: AuditWriter(session, "operator", "baseline").write("test.baseline", "configuration", None, {}) session.commit() fresh = semantic_fingerprint(session.connection()) previous = _without_lab_execution(_without_projects( _without_managed_identities( _without_secrets( _without_model_updates( _without_selected_comparisons( _without_creative(_without_model_changes(copy.deepcopy(fresh))) ) ) ) ), legacy=True, )) previous["tables"] = {key: value for key, value in previous["tables"].items() if key not in CONFIGURATION_TABLES} previous["physical_tables"] = [name for name in previous["physical_tables"] if name not in CONFIGURATION_TABLES] upgrade = fingerprint_compatibility(previous, fresh, source_schema_revision="20260830_0024") assert upgrade.compatible assert audit_fingerprint_differences_are_compatible(previous, fresh, upgrade) subject = ConfigurationService(session, Settings(_env_file=None), AuditContext("operator", "config", "restore-test")) change = ConfigurationChange(expected_revision=0, changes={"gateway_max_batch_size": 2}) preview = subject.preview(change) subject.apply(ConfigurationApply(**change.model_dump(), preview_digest=preview.preview_digest, confirm=True)) configured = semantic_fingerprint(session.connection()) configured_0033 = _without_lab_execution(_without_model_changes(configured)) assert not fingerprint_compatibility(previous, configured, source_schema_revision="20260830_0024").compatible current = fingerprint_compatibility( configured_0033, configured_0033, source_schema_revision=SELECTED_COMPARISON_SCHEMA, ) assert current.compatible assert audit_fingerprint_differences_are_compatible(configured_0033, configured_0033, current) for table in CONFIGURATION_TABLES: altered = copy.deepcopy(configured_0033) altered["tables"][table]["digest"] = "0" * 64 assert not audit_fingerprint_differences_are_compatible(configured_0033, altered, current) missing = copy.deepcopy(configured_0033) missing["tables"].pop(table) assert not fingerprint_compatibility( configured_0033, missing, source_schema_revision=SELECTED_COMPARISON_SCHEMA, ).compatible engine.dispose()