fix(cli): bootstrap registry database automatically
CI / lint (pull_request) Failing after 38s
CI / typecheck (pull_request) Successful in 47s
CI / quality (pull_request) Successful in 36s
CI / push-validation (pull_request) Successful in 27s
CI / helm (pull_request) Successful in 35s
CI / build (pull_request) Successful in 41s
CI / security (pull_request) Successful in 1m38s
CI / coverage (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 3m24s
CI / integration_tests (pull_request) Successful in 4m18s
CI / unit_tests (pull_request) Successful in 6m15s
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
CI / lint (pull_request) Failing after 38s
CI / typecheck (pull_request) Successful in 47s
CI / quality (pull_request) Successful in 36s
CI / push-validation (pull_request) Successful in 27s
CI / helm (pull_request) Successful in 35s
CI / build (pull_request) Successful in 41s
CI / security (pull_request) Successful in 1m38s
CI / coverage (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 3m24s
CI / integration_tests (pull_request) Successful in 4m18s
CI / unit_tests (pull_request) Successful in 6m15s
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
Ensure tool and validation CLI commands initialize the SQLite persistence layer on first use and add regression coverage.\n\nISSUES CLOSED: #6885
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""Step definitions for TDD Issue #6885 — CLI registry bootstrap."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from behave import given, then, when
|
||||
from typer.testing import CliRunner
|
||||
|
||||
import cleveragents.cli.bootstrap as cli_bootstrap
|
||||
from cleveragents.application.container import reset_container
|
||||
from cleveragents.cli.commands.tool import app as tool_app
|
||||
from cleveragents.cli.commands.validation import app as validation_app
|
||||
from cleveragents.config.settings import Settings
|
||||
|
||||
|
||||
def _reset_settings() -> None:
|
||||
"""Reset singleton settings between scenarios."""
|
||||
|
||||
Settings._instance = None # type: ignore[attr-defined]
|
||||
|
||||
|
||||
@given("a CLI runner without a bootstrapped registry database")
|
||||
def step_no_bootstrap(context) -> None:
|
||||
context.runner = CliRunner()
|
||||
|
||||
reset_container()
|
||||
_reset_settings()
|
||||
|
||||
cli_bootstrap._database_bootstrapped = False # type: ignore[attr-defined]
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix="tdd_tool_cli_bootstrap_6885_")
|
||||
db_path = Path(tmpdir) / "registry.db"
|
||||
|
||||
context._tool_cli_tmpdir = tmpdir
|
||||
context._tool_cli_db_path = db_path
|
||||
|
||||
os.environ["CLEVERAGENTS_DATABASE_URL"] = f"sqlite:///{db_path}"
|
||||
|
||||
def _cleanup() -> None:
|
||||
os.environ.pop("CLEVERAGENTS_DATABASE_URL", None)
|
||||
cli_bootstrap._database_bootstrapped = False # type: ignore[attr-defined]
|
||||
reset_container()
|
||||
_reset_settings()
|
||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||
|
||||
context.add_cleanup(_cleanup)
|
||||
|
||||
|
||||
@when("I invoke tool list without prior bootstrap")
|
||||
def step_invoke_tool_list(context) -> None:
|
||||
context.result = context.runner.invoke(tool_app, ["list"])
|
||||
|
||||
|
||||
@when("I invoke validation add without prior bootstrap")
|
||||
def step_invoke_validation_add(context) -> None:
|
||||
config_path = Path(context._tool_cli_tmpdir) / "validation.yaml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
name: local/test-validation
|
||||
description: temporary validation for TDD issue 6885
|
||||
source: custom
|
||||
mode: informational
|
||||
code: |
|
||||
def run(inputs):
|
||||
return {"passed": True}
|
||||
""".strip()
|
||||
)
|
||||
|
||||
context.result = context.runner.invoke(
|
||||
validation_app,
|
||||
[
|
||||
"add",
|
||||
"--config",
|
||||
str(config_path),
|
||||
"--format",
|
||||
"json",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@then("the tool list command should exit successfully")
|
||||
def step_tool_list_exit_ok(context) -> None:
|
||||
assert context.result.exit_code == 0, (
|
||||
f"Expected exit code 0, got {context.result.exit_code}.\n"
|
||||
f"Output:\n{context.result.output}\n"
|
||||
f"Exception: {getattr(context.result, 'exception', None)!r}"
|
||||
)
|
||||
|
||||
|
||||
@then("the validation add command should exit successfully")
|
||||
def step_validation_add_exit_ok(context) -> None:
|
||||
assert context.result.exit_code == 0, (
|
||||
f"Expected exit code 0, got {context.result.exit_code}.\n"
|
||||
f"Output:\n{context.result.output}\n"
|
||||
f"Exception: {getattr(context.result, 'exception', None)!r}"
|
||||
)
|
||||
|
||||
|
||||
@then("the tool list output should indicate that no tools are registered")
|
||||
def step_tool_list_output(context) -> None:
|
||||
output = context.result.output
|
||||
assert "No tools found" in output, (
|
||||
"Expected 'No tools found' in output.\n"
|
||||
f"Actual output:\n{output}"
|
||||
)
|
||||
|
||||
|
||||
@then("the validation add output should report the registered validation in JSON")
|
||||
def step_validation_add_output(context) -> None:
|
||||
output = context.result.output
|
||||
assert '"name": "local/test-validation"' in output, (
|
||||
"Expected the registered validation name in the JSON output."
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
@tdd_issue @tdd_issue_6885
|
||||
Feature: TDD Issue #6885 — Tool CLI bootstraps database automatically
|
||||
As a developer
|
||||
I want `agents tool list` and `agents validation add` to work on a fresh install
|
||||
So that users do not have to run a manual database upgrade before using the registry
|
||||
|
||||
Scenario: Tool list command bootstraps the database automatically
|
||||
Given a CLI runner without a bootstrapped registry database
|
||||
When I invoke tool list without prior bootstrap
|
||||
Then the tool list command should exit successfully
|
||||
And the tool list output should indicate that no tools are registered
|
||||
|
||||
Scenario: Validation add command bootstraps the database automatically
|
||||
Given a CLI runner without a bootstrapped registry database
|
||||
When I invoke validation add without prior bootstrap
|
||||
Then the validation add command should exit successfully
|
||||
And the validation add output should report the registered validation in JSON
|
||||
@@ -0,0 +1,39 @@
|
||||
"""CLI bootstrap helpers.
|
||||
|
||||
Ensures process-wide initialization for CLI commands that depend on
|
||||
persistence-backed registries by running Alembic migrations exactly once per
|
||||
process.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from threading import Lock
|
||||
|
||||
_database_bootstrapped = False
|
||||
_bootstrap_lock = Lock()
|
||||
|
||||
|
||||
def ensure_cli_database_bootstrapped(force: bool = False) -> None:
|
||||
"""Ensure CLI database schema exists and migrations are applied."""
|
||||
|
||||
global _database_bootstrapped
|
||||
|
||||
if _database_bootstrapped and not force:
|
||||
return
|
||||
|
||||
with _bootstrap_lock:
|
||||
if _database_bootstrapped and not force:
|
||||
return
|
||||
|
||||
from cleveragents.application.container import get_database_url
|
||||
from cleveragents.infrastructure.database.migration_runner import (
|
||||
MigrationRunner,
|
||||
)
|
||||
|
||||
runner = MigrationRunner(get_database_url())
|
||||
runner.init_or_upgrade(require_confirmation=False)
|
||||
|
||||
_database_bootstrapped = True
|
||||
|
||||
|
||||
__all__ = ["ensure_cli_database_bootstrapped"]
|
||||
@@ -51,6 +51,8 @@ from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
|
||||
import typer
|
||||
|
||||
from cleveragents.cli.bootstrap import ensure_cli_database_bootstrapped
|
||||
import yaml
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
@@ -76,6 +78,8 @@ def _get_tool_registry_service() -> Any:
|
||||
"""Get the ToolRegistryService from the container."""
|
||||
from cleveragents.application.container import get_container
|
||||
|
||||
ensure_cli_database_bootstrapped()
|
||||
|
||||
container = get_container()
|
||||
database_url: str = container.database_url()
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
|
||||
import typer
|
||||
|
||||
from cleveragents.cli.bootstrap import ensure_cli_database_bootstrapped
|
||||
import yaml
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
@@ -81,6 +83,8 @@ def _get_tool_registry_service() -> Any:
|
||||
"""
|
||||
from cleveragents.application.container import get_container
|
||||
|
||||
ensure_cli_database_bootstrapped()
|
||||
|
||||
return get_container().tool_registry_service()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user