fix(invariant): neutralise legacy m3_001 migration + drop stale model fixtures
Commit f2626d66e removed the legacy ``InvariantModel`` (id/description/
is_active) and rewired ``InvariantModel`` onto the new ``id/text/scope/
source_name/active/non_overridable`` schema owned by
``m11_001_standalone_invariants``. Two pieces of the previous design
remained behind and were breaking CI:
1. ``m3_001_invariants_table`` still issued ``op.create_table('invariants',
...)`` for the obsolete column set. When the upgrade chain reached
``m11_001_standalone_invariants`` the same table was created a second
time, raising ``OperationalError: table invariants already exists`` in
``before_scenario`` for every behave + robot scenario that
initialises the test database. That is the root cause of the 119
``errored`` scenarios in the CI ``unit_tests`` job and the
integration_tests collapse on PR #8684.
Turn ``m3_001`` into a no-op ``upgrade``/``downgrade`` so the
historical revision id stays reachable by the downstream merge
migrations (``m3_002_merge_invariants_and_a5_006`` and
``m9_004_merge_invariants_branch``) without touching the new
schema that ``m11_001`` now owns.
2. ``robot/invariant_model.robot`` + ``robot/helper_invariant_model.py``
and ``features/invariant_model.feature`` +
``features/steps/invariant_model_steps.py`` were smoke-tests written
for the removed legacy schema (``description``, ``is_active``, UUID
ids). They instantiate ``InvariantModel(description=..., is_active=...)``
and assert an ``ix_invariants_is_active`` index that the new schema
does not have, so every scenario in the suite fails with
``TypeError: 'description' is an invalid keyword argument``. The
real persistence contract is already covered by
``features/tdd_invariant_persistence.feature``,
``robot/tdd_invariant_persistence.robot`` and
``robot/invariant_cli.robot`` against the current schema; the
legacy-schema fixtures have no remaining users (verified via
``grep -rn helper_invariant_model``). Delete them.
Also runs ``ruff format`` on ``models.py`` to drop a trailing blank
line that was tripping the ``format`` nox session in CI ``lint``.
Verified locally:
* ``ruff format --check .`` -> 2298 files already formatted.
* ``ruff check src/ scripts/ examples/ features/ robot/ .opencode/``
-> All checks passed.
* ``nox -s unit_tests-3.13 -- features/tdd_invariant_persistence.feature``
-> 4 scenarios passed, 0 failed, 0 errored.
* ``nox -s integration_tests-3.13 -- robot/tdd_invariant_persistence.robot
robot/invariant_cli.robot robot/`` -> only the 8 deleted
``Suites.Robot.Invariant Model`` cases failed; deleting the suite
removes them entirely.
ISSUES CLOSED: #8573
This commit is contained in:
@@ -1,188 +0,0 @@
|
||||
"""Helper script for Robot Framework invariant model smoke tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure src is importable when run from workspace root
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from sqlalchemy import create_engine, inspect
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from cleveragents.infrastructure.database.models import Base, InvariantModel
|
||||
|
||||
|
||||
def _make_db() -> tuple[object, object]:
|
||||
"""Create an in-memory SQLite DB and return (engine, session)."""
|
||||
engine = create_engine("sqlite:///:memory:", echo=False)
|
||||
Base.metadata.create_all(engine)
|
||||
sm = sessionmaker(bind=engine)
|
||||
session = sm()
|
||||
return engine, session
|
||||
|
||||
|
||||
def _make_invariant(description: str, is_active: bool = True) -> InvariantModel:
|
||||
return InvariantModel(
|
||||
id=str(uuid.uuid4()),
|
||||
description=description,
|
||||
created_at=datetime.now(tz=UTC).isoformat(),
|
||||
is_active=is_active,
|
||||
)
|
||||
|
||||
|
||||
def _test_create_invariant() -> None:
|
||||
"""Create an Invariant with all required fields and verify persistence."""
|
||||
_, session = _make_db()
|
||||
inv = _make_invariant("All plans must have a goal")
|
||||
session.add(inv)
|
||||
session.commit()
|
||||
|
||||
retrieved = session.query(InvariantModel).filter_by(id=inv.id).first()
|
||||
assert retrieved is not None, "Invariant not found after persist"
|
||||
assert retrieved.description == "All plans must have a goal"
|
||||
print("invariant-create-ok")
|
||||
|
||||
|
||||
def _test_default_is_active() -> None:
|
||||
"""Verify that is_active defaults to True."""
|
||||
_, session = _make_db()
|
||||
inv = _make_invariant("Default active invariant")
|
||||
session.add(inv)
|
||||
session.commit()
|
||||
|
||||
retrieved = session.query(InvariantModel).filter_by(id=inv.id).first()
|
||||
assert retrieved is not None
|
||||
assert retrieved.is_active is True or retrieved.is_active == 1, (
|
||||
f"Expected is_active=True, got {retrieved.is_active!r}"
|
||||
)
|
||||
print("invariant-default-active-ok")
|
||||
|
||||
|
||||
def _test_created_at() -> None:
|
||||
"""Verify that created_at is populated on insert."""
|
||||
_, session = _make_db()
|
||||
inv = _make_invariant("Timestamped invariant")
|
||||
session.add(inv)
|
||||
session.commit()
|
||||
|
||||
retrieved = session.query(InvariantModel).filter_by(id=inv.id).first()
|
||||
assert retrieved is not None
|
||||
assert retrieved.created_at, "created_at should not be empty"
|
||||
assert len(str(retrieved.created_at)) > 0
|
||||
print("invariant-created-at-ok")
|
||||
|
||||
|
||||
def _test_uuid_id() -> None:
|
||||
"""Verify that the Invariant id is a valid UUID string."""
|
||||
_, session = _make_db()
|
||||
inv = _make_invariant("UUID invariant")
|
||||
session.add(inv)
|
||||
session.commit()
|
||||
|
||||
retrieved = session.query(InvariantModel).filter_by(id=inv.id).first()
|
||||
assert retrieved is not None
|
||||
try:
|
||||
uuid.UUID(str(retrieved.id))
|
||||
except ValueError as exc:
|
||||
raise AssertionError(f"id '{retrieved.id}' is not a valid UUID") from exc
|
||||
print("invariant-uuid-ok")
|
||||
|
||||
|
||||
def _test_query_active() -> None:
|
||||
"""Query Invariants filtered by is_active=True."""
|
||||
_, session = _make_db()
|
||||
for i in range(3):
|
||||
session.add(_make_invariant(f"Active {i}", is_active=True))
|
||||
for i in range(2):
|
||||
session.add(_make_invariant(f"Inactive {i}", is_active=False))
|
||||
session.commit()
|
||||
|
||||
results = session.query(InvariantModel).filter_by(is_active=True).all()
|
||||
assert len(results) == 3, f"Expected 3 active, got {len(results)}"
|
||||
print("invariant-query-active-ok")
|
||||
|
||||
|
||||
def _test_query_inactive() -> None:
|
||||
"""Query Invariants filtered by is_active=False."""
|
||||
_, session = _make_db()
|
||||
for i in range(3):
|
||||
session.add(_make_invariant(f"Active {i}", is_active=True))
|
||||
for i in range(2):
|
||||
session.add(_make_invariant(f"Inactive {i}", is_active=False))
|
||||
session.commit()
|
||||
|
||||
results = session.query(InvariantModel).filter_by(is_active=False).all()
|
||||
assert len(results) == 2, f"Expected 2 inactive, got {len(results)}"
|
||||
print("invariant-query-inactive-ok")
|
||||
|
||||
|
||||
def _test_deactivate() -> None:
|
||||
"""Set is_active to False on an existing Invariant."""
|
||||
_, session = _make_db()
|
||||
inv = _make_invariant("Active invariant to deactivate", is_active=True)
|
||||
session.add(inv)
|
||||
session.commit()
|
||||
|
||||
retrieved = session.query(InvariantModel).filter_by(id=inv.id).first()
|
||||
assert retrieved is not None
|
||||
retrieved.is_active = False
|
||||
session.commit()
|
||||
|
||||
updated = session.query(InvariantModel).filter_by(id=inv.id).first()
|
||||
assert updated is not None
|
||||
assert updated.is_active is False or updated.is_active == 0, (
|
||||
f"Expected is_active=False, got {updated.is_active!r}"
|
||||
)
|
||||
print("invariant-deactivate-ok")
|
||||
|
||||
|
||||
def _test_table_exists() -> None:
|
||||
"""Verify the invariants table exists after schema creation."""
|
||||
engine, _ = _make_db()
|
||||
inspector = inspect(engine)
|
||||
tables = inspector.get_table_names()
|
||||
assert "invariants" in tables, f"Table 'invariants' not found. Available: {tables}"
|
||||
print("invariant-table-ok")
|
||||
|
||||
|
||||
def _test_index_exists() -> None:
|
||||
"""Verify the index on is_active exists after schema creation."""
|
||||
engine, _ = _make_db()
|
||||
inspector = inspect(engine)
|
||||
indexes = inspector.get_indexes("invariants")
|
||||
index_names = [idx["name"] for idx in indexes]
|
||||
assert any("is_active" in name for name in index_names), (
|
||||
f"No index on is_active found. Indexes: {index_names}"
|
||||
)
|
||||
print("invariant-index-ok")
|
||||
|
||||
|
||||
_TESTS = {
|
||||
"create_invariant": _test_create_invariant,
|
||||
"default_is_active": _test_default_is_active,
|
||||
"created_at": _test_created_at,
|
||||
"uuid_id": _test_uuid_id,
|
||||
"query_active": _test_query_active,
|
||||
"query_inactive": _test_query_inactive,
|
||||
"deactivate": _test_deactivate,
|
||||
"table_exists": _test_table_exists,
|
||||
"index_exists": _test_index_exists,
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <test_name>")
|
||||
print(f"Available tests: {', '.join(sorted(_TESTS))}")
|
||||
sys.exit(1)
|
||||
|
||||
test_name = sys.argv[1]
|
||||
if test_name not in _TESTS:
|
||||
print(f"Unknown test: {test_name}")
|
||||
print(f"Available: {', '.join(sorted(_TESTS))}")
|
||||
sys.exit(1)
|
||||
|
||||
_TESTS[test_name]()
|
||||
@@ -1,63 +0,0 @@
|
||||
*** Settings ***
|
||||
Documentation Smoke tests for Invariant data model persistence contract
|
||||
Resource ${CURDIR}/common.resource
|
||||
Suite Setup Setup Test Environment With Database Isolation
|
||||
Suite Teardown Cleanup Test Environment
|
||||
|
||||
*** Variables ***
|
||||
${HELPER_SCRIPT} robot/helper_invariant_model.py
|
||||
|
||||
*** Test Cases ***
|
||||
Create Invariant With Required Fields
|
||||
[Documentation] Create an Invariant with all required fields and verify persistence
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} create_invariant cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0 msg=create_invariant failed: ${result.stderr}
|
||||
Should Contain ${result.stdout} invariant-create-ok
|
||||
|
||||
Is Active Defaults To True
|
||||
[Documentation] Verify that is_active defaults to True on a new Invariant
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} default_is_active cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0 msg=default_is_active failed: ${result.stderr}
|
||||
Should Contain ${result.stdout} invariant-default-active-ok
|
||||
|
||||
Created At Is Populated
|
||||
[Documentation] Verify that created_at is auto-populated on insert
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} created_at cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0 msg=created_at failed: ${result.stderr}
|
||||
Should Contain ${result.stdout} invariant-created-at-ok
|
||||
|
||||
Id Is UUID
|
||||
[Documentation] Verify that the Invariant id is a valid UUID string
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} uuid_id cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0 msg=uuid_id failed: ${result.stderr}
|
||||
Should Contain ${result.stdout} invariant-uuid-ok
|
||||
|
||||
Query Active Invariants
|
||||
[Documentation] Query Invariants filtered by is_active=True
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} query_active cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0 msg=query_active failed: ${result.stderr}
|
||||
Should Contain ${result.stdout} invariant-query-active-ok
|
||||
|
||||
Query Inactive Invariants
|
||||
[Documentation] Query Invariants filtered by is_active=False
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} query_inactive cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0 msg=query_inactive failed: ${result.stderr}
|
||||
Should Contain ${result.stdout} invariant-query-inactive-ok
|
||||
|
||||
Deactivate Invariant
|
||||
[Documentation] Set is_active to False on an existing Invariant
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} deactivate cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0 msg=deactivate failed: ${result.stderr}
|
||||
Should Contain ${result.stdout} invariant-deactivate-ok
|
||||
|
||||
Table Exists After Migration
|
||||
[Documentation] Verify the invariants table exists after schema creation
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} table_exists cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0 msg=table_exists failed: ${result.stderr}
|
||||
Should Contain ${result.stdout} invariant-table-ok
|
||||
|
||||
Index On Is Active Exists
|
||||
[Documentation] Verify the index on is_active exists after schema creation
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} index_exists cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0 msg=index_exists failed: ${result.stderr}
|
||||
Should Contain ${result.stdout} invariant-index-ok
|
||||
Reference in New Issue
Block a user