Initial public ModelForge release
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import Engine
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from alembic.config import Config
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.operations import Operations
|
||||
from alembic.script import ScriptDirectory
|
||||
from modelforge_api.domain.release import TARGET_SCHEMA_REVISION
|
||||
from modelforge_api.persistence.models import NodeCredential, NodeEnrollment
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MIGRATION_PATH = (
|
||||
ROOT
|
||||
/ "backend"
|
||||
/ "alembic"
|
||||
/ "versions"
|
||||
/ "20260830_0023_node_auth_scopes.py"
|
||||
)
|
||||
|
||||
|
||||
def _migration() -> ModuleType:
|
||||
spec = importlib.util.spec_from_file_location("node_auth_scopes_0023", MIGRATION_PATH)
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def _scope_database(
|
||||
*, enrollment_scope: str = "node.enroll", credential_scope: str = "node.publish"
|
||||
) -> Engine:
|
||||
engine = sa.create_engine("sqlite+pysqlite:///:memory:")
|
||||
metadata = sa.MetaData()
|
||||
enrollments = sa.Table(
|
||||
"node_enrollments",
|
||||
metadata,
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("scope", sa.String(64), nullable=False),
|
||||
)
|
||||
credentials = sa.Table(
|
||||
"node_credentials",
|
||||
metadata,
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("scope", sa.String(64), nullable=False),
|
||||
)
|
||||
metadata.create_all(engine)
|
||||
with engine.begin() as connection:
|
||||
connection.execute(enrollments.insert(), {"id": "enrollment", "scope": enrollment_scope})
|
||||
connection.execute(credentials.insert(), {"id": "credential", "scope": credential_scope})
|
||||
return engine
|
||||
|
||||
|
||||
def _run_migration(module: ModuleType, engine: Engine, action: str) -> None:
|
||||
with engine.begin() as connection:
|
||||
module.op = Operations(MigrationContext.configure(connection))
|
||||
getattr(module, action)()
|
||||
|
||||
|
||||
def _check_constraint_names(engine: Engine, table_name: str) -> set[str | None]:
|
||||
return {item["name"] for item in sa.inspect(engine).get_check_constraints(table_name)}
|
||||
|
||||
|
||||
def test_0023_is_the_linear_auth_step_before_the_current_audit_head() -> None:
|
||||
config = Config(str(ROOT / "backend" / "alembic.ini"))
|
||||
config.set_main_option("script_location", str(ROOT / "backend" / "alembic"))
|
||||
config.set_main_option("path_separator", "os")
|
||||
scripts = ScriptDirectory.from_config(config)
|
||||
migration = _migration()
|
||||
|
||||
assert scripts.get_heads() == ["20260830_0024"]
|
||||
assert migration.down_revision == "20260828_0022"
|
||||
assert scripts.get_revision("20260830_0024").down_revision == "20260830_0023"
|
||||
assert TARGET_SCHEMA_REVISION == "20260830_0024"
|
||||
|
||||
|
||||
def test_current_model_metadata_carries_both_exact_scope_constraints() -> None:
|
||||
enrollment_constraints = {constraint.name for constraint in NodeEnrollment.__table__.constraints}
|
||||
credential_constraints = {constraint.name for constraint in NodeCredential.__table__.constraints}
|
||||
|
||||
assert "ck_node_enrollment_scope" in enrollment_constraints
|
||||
assert "ck_node_credential_scope" in credential_constraints
|
||||
|
||||
|
||||
def test_0023_upgrades_correct_rows_enforces_scopes_and_downgrades_cleanly() -> None:
|
||||
migration = _migration()
|
||||
engine = _scope_database()
|
||||
|
||||
_run_migration(migration, engine, "upgrade")
|
||||
assert _check_constraint_names(engine, "node_enrollments") == {
|
||||
"ck_node_enrollment_scope"
|
||||
}
|
||||
assert _check_constraint_names(engine, "node_credentials") == {
|
||||
"ck_node_credential_scope"
|
||||
}
|
||||
|
||||
metadata = sa.MetaData()
|
||||
metadata.reflect(engine)
|
||||
with engine.begin() as connection:
|
||||
with pytest.raises(IntegrityError):
|
||||
connection.execute(
|
||||
metadata.tables["node_enrollments"].insert(),
|
||||
{"id": "wrong-enrollment", "scope": "node.publish"},
|
||||
)
|
||||
with pytest.raises(IntegrityError):
|
||||
connection.execute(
|
||||
metadata.tables["node_credentials"].insert(),
|
||||
{"id": "wrong-credential", "scope": "node.enroll"},
|
||||
)
|
||||
|
||||
_run_migration(migration, engine, "downgrade")
|
||||
assert _check_constraint_names(engine, "node_enrollments") == set()
|
||||
assert _check_constraint_names(engine, "node_credentials") == set()
|
||||
|
||||
metadata = sa.MetaData()
|
||||
metadata.reflect(engine)
|
||||
with engine.begin() as connection:
|
||||
connection.execute(
|
||||
metadata.tables["node_enrollments"].insert(),
|
||||
{"id": "downgraded-enrollment", "scope": "node.publish"},
|
||||
)
|
||||
connection.execute(
|
||||
metadata.tables["node_credentials"].insert(),
|
||||
{"id": "downgraded-credential", "scope": "node.enroll"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("enrollment_scope", "credential_scope", "malformed_table"),
|
||||
[
|
||||
("node.publish", "node.publish", "node_enrollments"),
|
||||
("node.enroll", "node.enroll", "node_credentials"),
|
||||
],
|
||||
)
|
||||
def test_0023_refuses_malformed_existing_scope_rows_before_ddl(
|
||||
enrollment_scope: str,
|
||||
credential_scope: str,
|
||||
malformed_table: str,
|
||||
) -> None:
|
||||
migration = _migration()
|
||||
engine = _scope_database(
|
||||
enrollment_scope=enrollment_scope,
|
||||
credential_scope=credential_scope,
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match=malformed_table):
|
||||
_run_migration(migration, engine, "upgrade")
|
||||
|
||||
assert _check_constraint_names(engine, "node_enrollments") == set()
|
||||
assert _check_constraint_names(engine, "node_credentials") == set()
|
||||
Reference in New Issue
Block a user