119 lines
3.6 KiB
Python
119 lines
3.6 KiB
Python
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()
|