from datetime import UTC, datetime import pytest from sqlalchemy import create_engine from sqlalchemy.orm import Session from modelforge_api.persistence.models import Base, Model, ModelRevision from modelforge_api.services.audit import AuditWriter def test_m0_schema_contains_required_domain_tables() -> None: required = { "models", "model_revisions", "model_artifacts", "derived_artifacts", "runtime_profiles", "deployments", "capabilities", "capability_contracts", "projects", "project_bindings", "compute_nodes", "accelerators", "benchmark_suites", "benchmark_runs", "experiments", "recommendations", "promotions", "migrations", "audit_events", "gpu_leases", "resource_envelopes", "host_telemetry_latest", "accelerator_telemetry_latest", "storage_volume_states", "hardware_inventory_runs", "node_enrollments", "node_credentials", "upstream_snapshots", "upstream_files", "artifact_sets", "download_plans", "download_plan_files", "artifact_jobs", "artifact_job_attempts", "artifact_inspections", "artifact_set_members", } assert required <= set(Base.metadata.tables) def test_audit_events_cannot_be_updated_or_deleted() -> None: engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(engine) with Session(engine) as session: event = AuditWriter(session, "operator", "test").write( "test", "model", None, {} ) session.commit() event.outcome = "changed" try: raised = False session.commit() except ValueError: raised = True session.rollback() assert raised def test_approved_revision_identity_is_immutable() -> None: engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(engine) with Session(engine) as session: model = Model( key="test", display_name="Test", upstream_provider="test", upstream_source="test/model", modalities=[], parameter_metadata={}, license_metadata={}, ) revision = ModelRevision( model=model, upstream_revision="main", resolved_commit_sha="a" * 40, metadata_snapshot={}, immutable_at=datetime.now(UTC), ) session.add_all([model, revision]) session.commit() revision.resolved_commit_sha = "b" * 40 with pytest.raises(ValueError, match="immutable approved fields"): session.commit() def test_accelerator_uuid_is_unique_per_node() -> None: from sqlalchemy.exc import IntegrityError from modelforge_api.persistence.models import Accelerator, ComputeNode engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(engine) with Session(engine) as session: node = ComputeNode(key="node", hostname="host", display_name="Host", identity_source="test") session.add(node) session.flush() session.add_all( [ Accelerator(compute_node_id=node.id, device_index=0, device_uuid="GPU-A", name="A"), Accelerator( compute_node_id=node.id, device_index=1, device_uuid="GPU-A", name="A again" ), ] ) with pytest.raises(IntegrityError): session.commit()