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:
2026-06-17 23:07:09 -04:00
committed by Forgejo
parent 4ed01ec0c1
commit 19dc9c231f
6 changed files with 19 additions and 569 deletions
-188
View File
@@ -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]()
-63
View File
@@ -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