ef905baff6
CI / lint (pull_request) Failing after 16s
CI / quality (pull_request) Successful in 17s
CI / typecheck (pull_request) Successful in 30s
CI / coverage (pull_request) Has been skipped
CI / security (pull_request) Successful in 30s
CI / build (pull_request) Successful in 14s
CI / integration_tests (pull_request) Successful in 5m13s
CI / unit_tests (pull_request) Successful in 17m8s
CI / docker (pull_request) Has been skipped
654 lines
22 KiB
Python
654 lines
22 KiB
Python
"""Step definitions for project_cli_commands.feature.
|
|
|
|
Exercises the *actual* CLI command functions in
|
|
``cleveragents.cli.commands.project`` (create, list, show, link-resource,
|
|
unlink-resource, delete) via monkey-patched helpers so that the command
|
|
bodies are executed under coverage.
|
|
|
|
Each scenario gets a fresh in-memory SQLite database. The DI-container
|
|
look-up helpers are monkey-patched to return repositories backed by that
|
|
database, then restored after every step that needs them.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from io import StringIO
|
|
from typing import Any
|
|
|
|
from behave import given, then, when # type: ignore[import-untyped]
|
|
from rich.console import Console
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Database / service bootstrap
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class _SharedSession:
|
|
"""Wraps a real SQLAlchemy Session but makes ``close()`` a no-op.
|
|
|
|
This ensures every repository call within the same scenario uses the
|
|
same session (and therefore sees the same uncommitted data), which is
|
|
critical for in-memory SQLite where transaction isolation would otherwise
|
|
hide data between sessions.
|
|
"""
|
|
|
|
def __init__(self, real: Any) -> None:
|
|
object.__setattr__(self, "_real", real)
|
|
|
|
def close(self) -> None:
|
|
"""No-op so the shared session stays usable across calls."""
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
return getattr(object.__getattribute__(self, "_real"), name)
|
|
|
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
setattr(object.__getattribute__(self, "_real"), name, value)
|
|
|
|
|
|
def _init_commands_db(context: Any) -> None:
|
|
"""Create a fresh in-memory database and wire up repos + service."""
|
|
from cleveragents.application.services.resource_registry_service import (
|
|
ResourceRegistryService,
|
|
)
|
|
from cleveragents.infrastructure.database.models import Base
|
|
from cleveragents.infrastructure.database.repositories import (
|
|
NamespacedProjectRepository,
|
|
ProjectResourceLinkRepository,
|
|
)
|
|
|
|
engine = create_engine(
|
|
"sqlite:///:memory:",
|
|
echo=False,
|
|
connect_args={"check_same_thread": False},
|
|
)
|
|
Base.metadata.create_all(engine)
|
|
|
|
# Use a single shared session to avoid transaction isolation issues
|
|
# with :memory: SQLite when multiple repos are involved.
|
|
real_session = sessionmaker(
|
|
bind=engine, expire_on_commit=False, autoflush=True, autocommit=False
|
|
)()
|
|
wrapper = _SharedSession(real_session)
|
|
|
|
def _shared_factory() -> Any:
|
|
return wrapper
|
|
|
|
context._cmd_engine = engine
|
|
context._cmd_factory = _shared_factory
|
|
context._cmd_project_repo = NamespacedProjectRepository(
|
|
session_factory=_shared_factory
|
|
)
|
|
context._cmd_link_repo = ProjectResourceLinkRepository(
|
|
session_factory=_shared_factory
|
|
)
|
|
context._cmd_resource_svc = ResourceRegistryService(session_factory=_shared_factory)
|
|
context._cmd_resource_svc.bootstrap_builtin_types()
|
|
context._cmd_output = ""
|
|
context._cmd_failed = False
|
|
context._cmd_resource_map = {}
|
|
context._cmd_last_resource_id = None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Monkey-patching helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_ORIG_FNS: dict[str, Any] = {}
|
|
|
|
|
|
def _patch_project_mod(context: Any) -> None:
|
|
"""Monkey-patch the four DI look-up helpers in project module."""
|
|
import cleveragents.cli.commands.project as project_mod
|
|
|
|
_ORIG_FNS["repo"] = project_mod._get_namespaced_project_repo
|
|
_ORIG_FNS["link"] = project_mod._get_resource_link_repo
|
|
_ORIG_FNS["svc"] = project_mod._get_resource_registry_service
|
|
|
|
project_mod._get_namespaced_project_repo = lambda: context._cmd_project_repo
|
|
project_mod._get_resource_link_repo = lambda: context._cmd_link_repo
|
|
project_mod._get_resource_registry_service = lambda: context._cmd_resource_svc
|
|
|
|
# Patch _store_project_extras to a no-op since it needs a shared
|
|
# file-based DB (the real helper is tested separately)
|
|
_ORIG_FNS["store_extras"] = project_mod._store_project_extras
|
|
project_mod._store_project_extras = lambda *a, **kw: None
|
|
|
|
|
|
def _unpatch_project_mod() -> None:
|
|
"""Restore original helpers."""
|
|
import cleveragents.cli.commands.project as project_mod
|
|
|
|
project_mod._get_namespaced_project_repo = _ORIG_FNS["repo"]
|
|
project_mod._get_resource_link_repo = _ORIG_FNS["link"]
|
|
project_mod._get_resource_registry_service = _ORIG_FNS["svc"]
|
|
if _ORIG_FNS.get("store_extras"):
|
|
project_mod._store_project_extras = _ORIG_FNS["store_extras"]
|
|
|
|
|
|
def _capture(context: Any, func: Any, *args: Any, **kwargs: Any) -> None:
|
|
"""Call a CLI function, capturing console output and success/failure."""
|
|
import typer
|
|
|
|
import cleveragents.cli.commands.project as project_mod
|
|
|
|
buf = StringIO()
|
|
fake_console = Console(file=buf, width=200, no_color=True)
|
|
|
|
orig_console = project_mod.console
|
|
orig_err = project_mod.err_console
|
|
project_mod.console = fake_console
|
|
project_mod.err_console = fake_console
|
|
|
|
_patch_project_mod(context)
|
|
failed = False
|
|
try:
|
|
func(*args, **kwargs)
|
|
except (SystemExit, typer.Exit, typer.Abort):
|
|
failed = True
|
|
except Exception:
|
|
failed = True
|
|
finally:
|
|
project_mod.console = orig_console
|
|
project_mod.err_console = orig_err
|
|
_unpatch_project_mod()
|
|
|
|
context._cmd_output = buf.getvalue()
|
|
context._cmd_failed = failed
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Reusable helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _create_project(context: Any, name: str, description: str | None = None) -> Any:
|
|
from cleveragents.domain.models.core.project import (
|
|
NamespacedProject,
|
|
parse_namespaced_name,
|
|
)
|
|
|
|
parsed = parse_namespaced_name(name)
|
|
proj = NamespacedProject(
|
|
name=parsed.name,
|
|
namespace=parsed.namespace,
|
|
server=parsed.server,
|
|
description=description,
|
|
)
|
|
context._cmd_project_repo.create(proj)
|
|
return proj
|
|
|
|
|
|
def _register_resource(context: Any, alias: str) -> str:
|
|
res = context._cmd_resource_svc.register_resource(
|
|
type_name="git-checkout",
|
|
name=f"local/{alias}",
|
|
location="/tmp/test-resource",
|
|
description=f"Test resource {alias}",
|
|
)
|
|
context._cmd_resource_map[alias] = res.resource_id
|
|
context._cmd_last_resource_id = res.resource_id
|
|
return res.resource_id
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Background
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a project CLI commands test database is initialized")
|
|
def step_init_commands_db(context: Any) -> None:
|
|
_init_commands_db(context)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('a project "{name}" is created in the commands DB')
|
|
def step_project_exists_cmd(context: Any, name: str) -> None:
|
|
_create_project(context, name)
|
|
|
|
|
|
@given('a resource "{alias}" is registered in the commands DB')
|
|
def step_resource_registered_cmd(context: Any, alias: str) -> None:
|
|
_register_resource(context, alias)
|
|
|
|
|
|
@given('a resource is linked to project "{project_name}" in the commands DB')
|
|
def step_resource_linked_cmd(context: Any, project_name: str) -> None:
|
|
safe = project_name.replace("/", "-")
|
|
rid = _register_resource(context, f"linked-{safe}")
|
|
context._cmd_link_repo.create_link(
|
|
project_name=project_name,
|
|
resource_id=rid,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helper function coverage
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I call _get_namespaced_project_repo via the patched container")
|
|
def step_call_get_repo(context: Any) -> None:
|
|
import cleveragents.cli.commands.project as project_mod
|
|
|
|
_patch_project_mod(context)
|
|
try:
|
|
context._cmd_helper_result = project_mod._get_namespaced_project_repo()
|
|
finally:
|
|
_unpatch_project_mod()
|
|
|
|
|
|
@when("I call _get_resource_link_repo via the patched container")
|
|
def step_call_get_link_repo(context: Any) -> None:
|
|
import cleveragents.cli.commands.project as project_mod
|
|
|
|
_patch_project_mod(context)
|
|
try:
|
|
context._cmd_helper_result = project_mod._get_resource_link_repo()
|
|
finally:
|
|
_unpatch_project_mod()
|
|
|
|
|
|
@when("I call _get_resource_registry_service via the patched container")
|
|
def step_call_get_svc(context: Any) -> None:
|
|
import cleveragents.cli.commands.project as project_mod
|
|
|
|
_patch_project_mod(context)
|
|
try:
|
|
context._cmd_helper_result = project_mod._get_resource_registry_service()
|
|
finally:
|
|
_unpatch_project_mod()
|
|
|
|
|
|
@when('I call _store_project_extras with invariants "{inv}" and actor "{actor}"')
|
|
def step_call_store_extras(context: Any, inv: str, actor: str) -> None:
|
|
"""Call the real _store_project_extras against a temp file-based DB.
|
|
|
|
``_store_project_extras`` opens its own engine from ``get_database_url()``,
|
|
so we create a file-based temp DB, seed a project, and patch the URL.
|
|
"""
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from sqlalchemy import create_engine as _ce
|
|
from sqlalchemy.orm import sessionmaker as _sm
|
|
|
|
from cleveragents.domain.models.core.project import (
|
|
NamespacedProject,
|
|
parse_namespaced_name,
|
|
)
|
|
from cleveragents.infrastructure.database.models import Base as _Base
|
|
from cleveragents.infrastructure.database.repositories import (
|
|
NamespacedProjectRepository,
|
|
)
|
|
|
|
tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False) # noqa: SIM115
|
|
tmp.close()
|
|
db_url = f"sqlite:///{tmp.name}"
|
|
engine = _ce(db_url, echo=False)
|
|
_Base.metadata.create_all(engine)
|
|
# Use a single session so we can commit after repo.create()
|
|
session = _sm(bind=engine, expire_on_commit=False)()
|
|
|
|
def _factory() -> Any:
|
|
return session
|
|
|
|
repo = NamespacedProjectRepository(session_factory=_factory)
|
|
parsed = parse_namespaced_name("local/extras-proj")
|
|
proj = NamespacedProject(name=parsed.name, namespace=parsed.namespace)
|
|
repo.create(proj)
|
|
session.commit()
|
|
|
|
import cleveragents.application.container as container_mod
|
|
import cleveragents.cli.commands.project as project_mod
|
|
|
|
orig = container_mod.get_database_url
|
|
container_mod.get_database_url = lambda: db_url
|
|
try:
|
|
project_mod._store_project_extras(
|
|
"local/extras-proj",
|
|
invariant_texts=[inv],
|
|
inv_actor=actor,
|
|
)
|
|
context._cmd_failed = False
|
|
except Exception:
|
|
context._cmd_failed = True
|
|
finally:
|
|
container_mod.get_database_url = orig
|
|
engine.dispose()
|
|
Path(tmp.name).unlink(missing_ok=True)
|
|
|
|
|
|
@then("the helper should return a NamespacedProjectRepository")
|
|
def step_check_repo(context: Any) -> None:
|
|
from cleveragents.infrastructure.database.repositories import (
|
|
NamespacedProjectRepository,
|
|
)
|
|
|
|
assert isinstance(context._cmd_helper_result, NamespacedProjectRepository)
|
|
|
|
|
|
@then("the helper should return a ProjectResourceLinkRepository")
|
|
def step_check_link_repo(context: Any) -> None:
|
|
from cleveragents.infrastructure.database.repositories import (
|
|
ProjectResourceLinkRepository,
|
|
)
|
|
|
|
assert isinstance(context._cmd_helper_result, ProjectResourceLinkRepository)
|
|
|
|
|
|
@then("the helper should return a ResourceRegistryService")
|
|
def step_check_svc(context: Any) -> None:
|
|
from cleveragents.application.services.resource_registry_service import (
|
|
ResourceRegistryService,
|
|
)
|
|
|
|
assert isinstance(context._cmd_helper_result, ResourceRegistryService)
|
|
|
|
|
|
@then("the extras call should complete without error")
|
|
def step_extras_ok(context: Any) -> None:
|
|
assert not context._cmd_failed, "Expected _store_project_extras to succeed"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Create command
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I invoke project-create triggering a database error")
|
|
def step_invoke_create_db_error(context: Any) -> None:
|
|
"""Exercise the ``except DatabaseError`` branch in the create command."""
|
|
from unittest.mock import MagicMock
|
|
|
|
from cleveragents.cli.commands.project import create
|
|
from cleveragents.core.exceptions import DatabaseError
|
|
|
|
# Temporarily replace the repo with one whose create() raises DatabaseError
|
|
orig_repo = context._cmd_project_repo
|
|
mock_repo = MagicMock()
|
|
mock_repo.create.side_effect = DatabaseError("simulated DB failure")
|
|
context._cmd_project_repo = mock_repo
|
|
try:
|
|
_capture(context, create, name="db-err-proj")
|
|
finally:
|
|
context._cmd_project_repo = orig_repo
|
|
|
|
|
|
@when('I invoke project-create with bare name "{name}"')
|
|
def step_invoke_create(context: Any, name: str) -> None:
|
|
from cleveragents.cli.commands.project import create
|
|
|
|
_capture(context, create, name=name)
|
|
|
|
|
|
@when('I invoke project-create with name "{name}" description "{desc}"')
|
|
def step_invoke_create_desc(context: Any, name: str, desc: str) -> None:
|
|
from cleveragents.cli.commands.project import create
|
|
|
|
_capture(context, create, name=name, description=desc)
|
|
|
|
|
|
@when('I invoke project-create with name "{name}" format "{fmt}"')
|
|
def step_invoke_create_fmt(context: Any, name: str, fmt: str) -> None:
|
|
from cleveragents.cli.commands.project import create
|
|
|
|
_capture(context, create, name=name, output_format=fmt)
|
|
|
|
|
|
@when('I invoke project-create with name "{name}" invariant "{inv}"')
|
|
def step_invoke_create_inv(context: Any, name: str, inv: str) -> None:
|
|
from cleveragents.cli.commands.project import create
|
|
|
|
_capture(context, create, name=name, invariant=[inv])
|
|
|
|
|
|
@when('I invoke project-create with name "{name}" linking resource "{res}"')
|
|
def step_invoke_create_res(context: Any, name: str, res: str) -> None:
|
|
from cleveragents.cli.commands.project import create
|
|
|
|
_capture(context, create, name=name, resource=[f"local/{res}"])
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# List command
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I invoke project-list with default options")
|
|
def step_invoke_list(context: Any) -> None:
|
|
from cleveragents.cli.commands.project import list_projects
|
|
|
|
_capture(context, list_projects)
|
|
|
|
|
|
@when('I invoke project-list with namespace "{ns}"')
|
|
def step_invoke_list_ns(context: Any, ns: str) -> None:
|
|
from cleveragents.cli.commands.project import list_projects
|
|
|
|
_capture(context, list_projects, namespace=ns)
|
|
|
|
|
|
@when('I invoke project-list with regex "{regex}"')
|
|
def step_invoke_list_regex(context: Any, regex: str) -> None:
|
|
from cleveragents.cli.commands.project import list_projects
|
|
|
|
_capture(context, list_projects, regex=regex)
|
|
|
|
|
|
@when('I invoke project-list with invalid regex "{regex}"')
|
|
def step_invoke_list_bad_regex(context: Any, regex: str) -> None:
|
|
from cleveragents.cli.commands.project import list_projects
|
|
|
|
_capture(context, list_projects, regex=regex)
|
|
|
|
|
|
@when('I invoke project-list with format "{fmt}"')
|
|
def step_invoke_list_fmt(context: Any, fmt: str) -> None:
|
|
from cleveragents.cli.commands.project import list_projects
|
|
|
|
_capture(context, list_projects, output_format=fmt)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Show command
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I invoke project-show for "{name}" default format')
|
|
def step_invoke_show(context: Any, name: str) -> None:
|
|
from cleveragents.cli.commands.project import show
|
|
|
|
_capture(context, show, project=name)
|
|
|
|
|
|
@when('I invoke project-show for "{name}" format "{fmt}"')
|
|
def step_invoke_show_fmt(context: Any, name: str, fmt: str) -> None:
|
|
from cleveragents.cli.commands.project import show
|
|
|
|
_capture(context, show, project=name, output_format=fmt)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Link-resource command
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I invoke project-link for "{proj}" resource "{res}" default format')
|
|
def step_invoke_link(context: Any, proj: str, res: str) -> None:
|
|
from cleveragents.cli.commands.project import link_resource
|
|
|
|
resource_name = f"local/{res}" if "/" not in res else res
|
|
_capture(context, link_resource, project=proj, resource_name=resource_name)
|
|
|
|
|
|
@when('I invoke project-link for "{proj}" resource "{res}" read-only')
|
|
def step_invoke_link_ro(context: Any, proj: str, res: str) -> None:
|
|
from cleveragents.cli.commands.project import link_resource
|
|
|
|
resource_name = f"local/{res}" if "/" not in res else res
|
|
_capture(
|
|
context,
|
|
link_resource,
|
|
project=proj,
|
|
resource_name=resource_name,
|
|
read_only=True,
|
|
)
|
|
|
|
|
|
@when('I invoke project-link for "{proj}" resource "{res}" format "{fmt}"')
|
|
def step_invoke_link_fmt(context: Any, proj: str, res: str, fmt: str) -> None:
|
|
from cleveragents.cli.commands.project import link_resource
|
|
|
|
resource_name = f"local/{res}" if "/" not in res else res
|
|
_capture(
|
|
context,
|
|
link_resource,
|
|
project=proj,
|
|
resource_name=resource_name,
|
|
output_format=fmt,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Unlink-resource command
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I invoke project-unlink for "{proj}" with yes default format')
|
|
def step_invoke_unlink(context: Any, proj: str) -> None:
|
|
from cleveragents.cli.commands.project import unlink_resource
|
|
|
|
links = context._cmd_link_repo.list_links(proj)
|
|
if links:
|
|
res_id = str(links[0].resource_id)
|
|
try:
|
|
res = context._cmd_resource_svc.show_resource(res_id)
|
|
res_name = res.name or res_id
|
|
except Exception:
|
|
res_name = res_id
|
|
_capture(
|
|
context,
|
|
unlink_resource,
|
|
project=proj,
|
|
resource_name=res_name,
|
|
yes=True,
|
|
)
|
|
else:
|
|
_capture(
|
|
context,
|
|
unlink_resource,
|
|
project=proj,
|
|
resource_name="none",
|
|
yes=True,
|
|
)
|
|
|
|
|
|
@when('I invoke project-unlink for "{proj}" with yes format "{fmt}"')
|
|
def step_invoke_unlink_fmt(context: Any, proj: str, fmt: str) -> None:
|
|
from cleveragents.cli.commands.project import unlink_resource
|
|
|
|
links = context._cmd_link_repo.list_links(proj)
|
|
if links:
|
|
res_id = str(links[0].resource_id)
|
|
try:
|
|
res = context._cmd_resource_svc.show_resource(res_id)
|
|
res_name = res.name or res_id
|
|
except Exception:
|
|
res_name = res_id
|
|
_capture(
|
|
context,
|
|
unlink_resource,
|
|
project=proj,
|
|
resource_name=res_name,
|
|
yes=True,
|
|
output_format=fmt,
|
|
)
|
|
else:
|
|
_capture(
|
|
context,
|
|
unlink_resource,
|
|
project=proj,
|
|
resource_name="none",
|
|
yes=True,
|
|
output_format=fmt,
|
|
)
|
|
|
|
|
|
@when('I invoke project-unlink for "{proj}" resource "{res}" with yes')
|
|
def step_invoke_unlink_named(context: Any, proj: str, res: str) -> None:
|
|
from cleveragents.cli.commands.project import unlink_resource
|
|
|
|
resource_name = f"local/{res}" if "/" not in res else res
|
|
_capture(
|
|
context,
|
|
unlink_resource,
|
|
project=proj,
|
|
resource_name=resource_name,
|
|
yes=True,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Delete command
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I invoke project-delete for "{name}" with yes')
|
|
def step_invoke_delete(context: Any, name: str) -> None:
|
|
from cleveragents.cli.commands.project import delete
|
|
|
|
_capture(context, delete, name=name, yes=True)
|
|
|
|
|
|
@when('I invoke project-delete for "{name}" with yes format "{fmt}"')
|
|
def step_invoke_delete_fmt(context: Any, name: str, fmt: str) -> None:
|
|
from cleveragents.cli.commands.project import delete
|
|
|
|
_capture(context, delete, name=name, yes=True, output_format=fmt)
|
|
|
|
|
|
@when('I invoke project-delete for "{name}" with yes no force')
|
|
def step_invoke_delete_no_force(context: Any, name: str) -> None:
|
|
from cleveragents.cli.commands.project import delete
|
|
|
|
_capture(context, delete, name=name, yes=True, force=False)
|
|
|
|
|
|
@when('I invoke project-delete for "{name}" with yes and force')
|
|
def step_invoke_delete_force(context: Any, name: str) -> None:
|
|
from cleveragents.cli.commands.project import delete
|
|
|
|
_capture(context, delete, name=name, yes=True, force=True)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then assertions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the project cmd output should contain "{text}"')
|
|
def step_cmd_output_contains(context: Any, text: str) -> None:
|
|
assert text.lower() in context._cmd_output.lower(), (
|
|
f"Expected output to contain '{text}', got:\n{context._cmd_output}"
|
|
)
|
|
|
|
|
|
@then("the project cmd should succeed")
|
|
def step_cmd_success(context: Any) -> None:
|
|
assert not context._cmd_failed, (
|
|
f"Expected command to succeed but it failed. Output:\n{context._cmd_output}"
|
|
)
|
|
|
|
|
|
@then("the project cmd should fail")
|
|
def step_cmd_fail(context: Any) -> None:
|
|
assert context._cmd_failed, (
|
|
f"Expected command to fail but it succeeded. Output:\n{context._cmd_output}"
|
|
)
|