test: add TDD bug-capture test for #1023 — implicit init requirement #1113

Merged
brent.edwards merged 3 commits from tdd/m4-e2e-implicit-init into master 2026-03-28 00:30:27 +00:00
5 changed files with 440 additions and 0 deletions
+6
View File
@@ -16,6 +16,12 @@
guidance propagation -- and verify cautious-profile confidence-threshold
pausing (S37262-37367) including a pause-and-resume flow. Facade stub
updated to echo guidance text. (#961)
- Added TDD bug-capture tests for bug #1023: CLI commands fail without explicit
`agents init` when `CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true` is set. Two
Behave BDD scenarios and two Robot Framework integration tests verify that
`resource add` and `project create` succeed in a fresh environment without
prior init. Tests use `@tdd_expected_fail` until the bug fix is merged.
(#1033)
- Added TDD bug-capture tests for bug #1076`use_action()` does not
propagate `automation_profile` to Plan. Three Behave BDD scenarios
(`@tdd_bug @tdd_bug_1076 @tdd_expected_fail`) verify the full precedence
@@ -0,0 +1,173 @@
"""Step definitions for TDD Bug #1023 — implicit init requirement.
These steps exercise real CLI commands in a fresh environment where
``agents init`` has NOT been explicitly run. The environment variable
``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true`` is set, which *should*
cause the database to be created implicitly.
Bug #1023 reports that ``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true``
triggers schema migrations on an existing database but does NOT create
the database file or its parent directory structure. As a result, any
CLI command that touches the database (e.g., ``resource add``,
``project create``) fails with:
sqlite3.OperationalError: unable to open database file
The assertions here expect the commands to succeed (exit code 0),
proving implicit init works and preventing regressions.
Mock strategy
~~~~~~~~~~~~~
No mocks are needed we exercise the real CLI via Typer's CliRunner
in a pristine temporary directory. We only manipulate environment
variables to create a fresh, uninitialised CLEVERAGENTS_HOME. We
remove ``CLEVERAGENTS_DATABASE_URL``, ``CLEVERAGENTS_TEST_DATABASE_URL``,
``BEHAVE_TESTING``, and ``CLEVERAGENTS_TEMPLATE_DB`` so the container
derives the DB path from ``CLEVERAGENTS_HOME``, migration prompts are
not auto-approved by the testing guard, and the template-DB fast-path
does not silently create the database for us.
"""
from __future__ import annotations
import os
import shutil
import tempfile
from behave import given, then, when
from behave.runner import Context
from typer.testing import CliRunner
from cleveragents.application.container import reset_container
from cleveragents.cli.main import app
runner = CliRunner()
# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------
@given("a fresh isolated environment for tdd-implicit-init")
def step_fresh_environment(context: Context) -> None:
"""Set up a fresh temporary environment with no existing database.
Creates a temporary directory to serve as ``CLEVERAGENTS_HOME``.
Does NOT run ``agents init`` the entire point of this TDD test is
to verify that CLI commands work without it.
Removes ``BEHAVE_TESTING`` so the migration runner's auto-approve
guard does not mask the real code path.
"""
tmpdir = tempfile.mkdtemp(prefix="tdd_implicit_init_")
context.tdd_implicit_init_tmpdir = tmpdir
# Save and remove env vars that point to pre-migrated databases,
# template-DB fast-paths, or testing guards that auto-approve
# migration prompts. CLEVERAGENTS_TEMPLATE_DB is particularly
# important: when set, the Behave template-DB patch copies a
# pre-migrated database, silently bypassing the directory-creation
# bug this test is designed to capture.
saved_env: dict[str, str | None] = {
k: os.environ.pop(k, None)
for k in (
"CLEVERAGENTS_AUTO_APPLY_MIGRATIONS",
"CLEVERAGENTS_DATABASE_URL",
"CLEVERAGENTS_TEST_DATABASE_URL",
"CLEVERAGENTS_HOME",
"BEHAVE_TESTING",
"CLEVERAGENTS_TEMPLATE_DB",
"CLEVERAGENTS_TESTING_USE_MOCK_AI",
)
}
context.tdd_implicit_init_saved_env = saved_env
# Point CLEVERAGENTS_HOME at our pristine temp directory.
os.environ["CLEVERAGENTS_HOME"] = tmpdir
# Force an isolated SQLite database path under this scenario's temp
# directory. This avoids leaking state from repository-level local
# database files created by prior test runs.
db_url = f"sqlite:///{tmpdir}/db/cleveragents.db"
os.environ["CLEVERAGENTS_DATABASE_URL"] = db_url
os.environ["CLEVERAGENTS_TEST_DATABASE_URL"] = db_url
# Reset the DI container so it picks up our env overrides.
reset_container()
def _cleanup() -> None:
for key, val in saved_env.items():
if val is not None:
os.environ[key] = val
else:
os.environ.pop(key, None)
reset_container()
shutil.rmtree(tmpdir, ignore_errors=True)
context.add_cleanup(_cleanup)
@given("CLEVERAGENTS_AUTO_APPLY_MIGRATIONS is set to true")
def step_set_auto_apply_migrations(context: Context) -> None:
"""Ensure CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true is in the environment.
This is the env var that should, in theory, trigger implicit
database creation and migration. Bug #1023 reports it does not
create the directory structure.
"""
os.environ["CLEVERAGENTS_AUTO_APPLY_MIGRATIONS"] = "true"
# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------
@when('I run the CLI command "{command}" without prior init')
def step_run_cli_command_without_init(context: Context, command: str) -> None:
"""Invoke a CLI command via CliRunner without running ``agents init`` first.
The command string is split into tokens and passed to the main CLI
app. ``CLEVERAGENTS_HOME`` is set to the pristine temp directory
and ``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true`` is in the
environment.
Exceptions are caught by CliRunner (the default behaviour) so that
the result object always carries an ``exit_code`` and ``output``.
This is essential for ``@tdd_expected_fail`` inversion if
exceptions propagated as non-AssertionError, the inversion guard
in ``apply_tdd_inversion`` would skip inversion and the scenario
would be reported as a hard failure instead of an expected failure.
"""
# Reset container again to ensure a clean UoW derivation.
reset_container()
args = command.split()
context.tdd_implicit_init_result = runner.invoke(app, args)
# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------
@then("the tdd-implicit-init command should exit with code {code:d}")
def step_exit_code(context: Context, code: int) -> None:
"""Assert the CLI command exited with the expected return code."""
result = context.tdd_implicit_init_result
assert result is not None, "CLI command was not invoked"
actual = result.exit_code
assert actual == code, (
f"Expected exit code {code}, got {actual}.\nOutput:\n{result.output}"
)
@then('the tdd-implicit-init command output should contain "{text}"')
def step_output_contains(context: Context, text: str) -> None:
"""Assert the CLI command output contains the expected text."""
result = context.tdd_implicit_init_result
assert result is not None, "CLI command was not invoked"
assert text in result.output, (
f"Expected output to contain {text!r}.\nActual output:\n{result.output}"
)
+35
View File
@@ -0,0 +1,35 @@
@tdd_expected_fail @tdd_issue @tdd_issue_1023
Feature: TDD Bug #1023 — CLI commands should succeed without explicit init
As a developer
I want to verify that CLI commands that touch the database succeed
without requiring an explicit `agents init` invocation first
So that the bug is captured and will be caught by a regression test
Bug #1023 reports that running any DB-dependent CLI command (e.g.,
`resource add`, `project create`) in a fresh environment fails with
`sqlite3.OperationalError: unable to open database file` even when
`CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true` is set. The specification
implies that initialization should happen implicitly.
The root cause is that `CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true`
triggers schema migrations on an existing database but does NOT create
the database file or its parent directory structure. A manual
`agents init --yes --force` is required first.
These tests assert the expected behaviour (implicit init) and will
fail until the bug is fixed. The @tdd_expected_fail tag inverts the
result so CI remains green while the defect is open.
Scenario: Resource add succeeds in a fresh environment without explicit init
Given a fresh isolated environment for tdd-implicit-init
And CLEVERAGENTS_AUTO_APPLY_MIGRATIONS is set to true
When I run the CLI command "resource add git-checkout local/tdd-test-resource --path . --branch main" without prior init
Then the tdd-implicit-init command should exit with code 0
And the tdd-implicit-init command output should contain "tdd-test-resource"
Scenario: Project create succeeds in a fresh environment without explicit init
Given a fresh isolated environment for tdd-implicit-init
And CLEVERAGENTS_AUTO_APPLY_MIGRATIONS is set to true
When I run the CLI command "project create local/tdd-test-project" without prior init
Then the tdd-implicit-init command should exit with code 0
And the tdd-implicit-init command output should contain "tdd-test-project"
+181
View File
@@ -0,0 +1,181 @@
"""Helper script for tdd_e2e_implicit_init.robot smoke tests.
Each subcommand exercises a real CLI command in a fresh environment
where ``agents init`` has NOT been run, to reproduce bug #1023.
Bug #1023 reports that ``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true``
triggers schema migrations on an existing database but does NOT create
the database file or its parent directory structure. As a result, any
CLI command that touches the database fails with:
sqlite3.OperationalError: unable to open database file
The helper reports the **real** outcome: it exits 0 and prints the
sentinel when the operation succeeds (bug is fixed), and exits 1 when
the bug is still present. The ``tdd_expected_fail_listener`` on the
Robot side handles pass/fail inversion while the bug remains open.
Mock strategy
~~~~~~~~~~~~~
No mocks are needed we exercise the real CLI via Typer's CliRunner
in a pristine temporary directory. We only manipulate environment
variables to create a fresh, uninitialised CLEVERAGENTS_HOME and set
``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true``.
"""
from __future__ import annotations
import os
import shutil
import sys
import tempfile
from collections.abc import Callable
from pathlib import Path
# Ensure local source tree is importable
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from typer.testing import CliRunner # noqa: E402
from cleveragents.application.container import reset_container # noqa: E402
from cleveragents.cli.main import app # noqa: E402
runner = CliRunner()
def _run_cli_without_init(args: list[str], sentinel: str) -> None:
"""Run a CLI command in a fresh environment without prior init.
Creates a pristine temp directory, sets CLEVERAGENTS_HOME to it,
enables CLEVERAGENTS_AUTO_APPLY_MIGRATIONS, and invokes the CLI.
Exits 0 with sentinel on success; exits 1 on failure.
Environment variables are always restored in the ``finally`` block
so subsequent tests are not polluted even if the CLI invocation
raises an unexpected exception.
Exceptions are caught by CliRunner (the default behaviour) so that
the result object always carries an ``exit_code`` and ``output``.
Using ``catch_exceptions=False`` would cause non-AssertionError
exceptions to propagate, preventing the ``tdd_expected_fail``
listener from inverting the result.
Parameters
----------
args:
CLI arguments to pass (e.g., ``["resource", "add", ...]``).
sentinel:
Sentinel string to print on success.
"""
tmpdir = tempfile.mkdtemp(prefix="tdd_implicit_init_robot_")
env_save: dict[str, str | None] = {}
try:
# Save and remove env vars that point to pre-migrated databases,
# template-DB fast-paths, or testing guards that auto-approve
# migration prompts. CLEVERAGENTS_TEMPLATE_DB is particularly
# important: when set, the Behave template-DB patch copies a
# pre-migrated database, silently bypassing the directory-
# creation bug this test is designed to capture.
for key in (
"CLEVERAGENTS_AUTO_APPLY_MIGRATIONS",
"CI",
"BEHAVE_TESTING",
"ROBOT_TESTING",
"CLEVERAGENTS_HOME",
"CLEVERAGENTS_DATABASE_URL",
"CLEVERAGENTS_TEST_DATABASE_URL",
"CLEVERAGENTS_TEMPLATE_DB",
"CLEVERAGENTS_TESTING_USE_MOCK_AI",
):
env_save[key] = os.environ.pop(key, None)
os.environ["CLEVERAGENTS_HOME"] = tmpdir
os.environ["CLEVERAGENTS_AUTO_APPLY_MIGRATIONS"] = "true"
# Reset DI container so it picks up our env overrides.
reset_container()
result = runner.invoke(app, args)
if result.exit_code != 0:
print(
f"CLI command failed with exit code {result.exit_code}",
file=sys.stderr,
)
print(f"Output: {result.output}", file=sys.stderr)
sys.exit(1)
# Bug fixed — command succeeded without explicit init.
print(sentinel)
finally:
# Always restore env vars and clean up the temp directory,
# even if runner.invoke() or assertions raise.
for key, val in env_save.items():
if val is not None:
os.environ[key] = val
else:
os.environ.pop(key, None)
reset_container()
shutil.rmtree(tmpdir, ignore_errors=True)
# ---------------------------------------------------------------------------
# Subcommands
# ---------------------------------------------------------------------------
def resource_add_no_init() -> None:
"""Invoke ``resource add`` without prior ``agents init``.
Exercises the ``resource add git-checkout`` command in a fresh
environment. If the database directory structure is not
auto-created, this will fail with OperationalError.
"""
_run_cli_without_init(
[
"resource",
"add",
"git-checkout",
"local/tdd-test-resource",
"--path",
".",
"--branch",
"main",
],
"tdd-resource-add-no-init-ok",
)
def project_create_no_init() -> None:
"""Invoke ``project create`` without prior ``agents init``.
Exercises the ``project create`` command in a fresh environment.
If the database directory structure is not auto-created, this
will fail with OperationalError.
"""
_run_cli_without_init(
["project", "create", "local/tdd-test-project"],
"tdd-project-create-no-init-ok",
)
# ---------------------------------------------------------------------------
# Dispatcher
# ---------------------------------------------------------------------------
_COMMANDS: dict[str, Callable[[], None]] = {
"resource-add-no-init": resource_add_no_init,
"project-create-no-init": project_create_no_init,
}
if __name__ == "__main__":
if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
print(
f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>",
file=sys.stderr,
)
sys.exit(1)
cmd = _COMMANDS[sys.argv[1]]
cmd()
+45
View File
@@ -0,0 +1,45 @@
*** Settings ***
Documentation TDD Bug #1023 — CLI commands should succeed without explicit init
... Integration smoke tests verifying that DB-dependent CLI commands
... (e.g., ``resource add``, ``project create``) succeed in a fresh
... environment where ``agents init`` has NOT been run, when
... ``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true`` is set.
...
... Bug #1023 reports that ``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true``
... triggers migrations on an existing database but does NOT create
... the database file or its parent directory structure.
Resource ${CURDIR}/common.resource
Suite Setup Setup Test Environment
Suite Teardown Cleanup Test Environment
*** Variables ***
${HELPER} ${CURDIR}/helper_tdd_e2e_implicit_init.py
*** Test Cases ***
TDD Resource Add Succeeds Without Explicit Init
[Documentation] Verify that ``resource add`` succeeds in a fresh
... environment without prior ``agents init`` when
... ``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true`` is set.
... The helper exits 0 with a sentinel when the command
... succeeds (bug is fixed), and exits 1 when the bug is
... present (OperationalError).
[Tags] tdd_bug tdd_bug_1023
${result}= Run Process ${PYTHON} ${HELPER} resource-add-no-init cwd=${WORKSPACE} timeout=120s on_timeout=kill
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} tdd-resource-add-no-init-ok
TDD Project Create Succeeds Without Explicit Init
[Documentation] Verify that ``project create`` succeeds in a fresh
... environment without prior ``agents init`` when
... ``CLEVERAGENTS_AUTO_APPLY_MIGRATIONS=true`` is set.
... The helper exits 0 with a sentinel when the command
... succeeds (bug is fixed), and exits 1 when the bug is
... present (OperationalError).
[Tags] tdd_bug tdd_bug_1023
${result}= Run Process ${PYTHON} ${HELPER} project-create-no-init cwd=${WORKSPACE} timeout=120s on_timeout=kill
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} tdd-project-create-no-init-ok