test(cli): preserve invariant scope coverage after rebase
This commit is contained in:
committed by
Forgejo
parent
5a711f9776
commit
26662836aa
@@ -1,25 +1,167 @@
|
|||||||
Feature: Invariant CLI new coverage for scope resolution and conflict detection
|
Feature: Invariant CLI commands coverage
|
||||||
|
As a developer
|
||||||
|
I want full test coverage for the invariant CLI commands module
|
||||||
|
So that all commands and helper functions are verified
|
||||||
|
|
||||||
The ``agents invariant`` CLI command group uses ``_resolve_scope()`` to
|
# === _resolve_scope helper ===
|
||||||
resolve mutually-exclusive scope flags (``--global``, ``--project``,
|
|
||||||
``--plan``, ``--action``). This feature tests that resolver directly,
|
|
||||||
covering both add-command usage paths and the shared list path.
|
|
||||||
|
|
||||||
Background:
|
Scenario: Resolve scope with --global flag returns GLOBAL scope
|
||||||
Given invariant service mock is configured
|
When I resolve invariant scope with global flag set
|
||||||
|
Then the resolved invariant scope should be "global"
|
||||||
|
And the resolved invariant source name should be "system"
|
||||||
|
|
||||||
Scenario: Resolve invariant with global-only flag returns GLOBAL scope
|
Scenario: Resolve scope with --project flag returns PROJECT scope
|
||||||
When I resolve invariant add scope with only global flag
|
When I resolve invariant scope with project "myapp"
|
||||||
Then invariant add with global flag returns GLOBAL scope
|
Then the resolved invariant scope should be "project"
|
||||||
|
And the resolved invariant source name should be "myapp"
|
||||||
|
|
||||||
Scenario: Resolve invariant with project-only flag returns PROJECT scope
|
Scenario: Resolve scope with --plan flag returns PLAN scope
|
||||||
When I resolve invariant add scope with only project flag
|
When I resolve invariant scope with plan "PLAN_01HX"
|
||||||
Then invariant add with project flag returns PROJECT scope
|
Then the resolved invariant scope should be "plan"
|
||||||
|
And the resolved invariant source name should be "PLAN_01HX"
|
||||||
|
|
||||||
|
Scenario: Resolve scope with --action flag returns ACTION scope
|
||||||
|
When I resolve invariant scope with action "deploy-service"
|
||||||
|
Then the resolved invariant scope should be "action"
|
||||||
|
And the resolved invariant source name should be "deploy-service"
|
||||||
|
|
||||||
|
Scenario: Resolve scope with no flags defaults to GLOBAL scope
|
||||||
|
When I resolve invariant scope with no flags
|
||||||
|
Then the resolved invariant scope should be "global"
|
||||||
|
And the resolved invariant source name should be "system"
|
||||||
|
|
||||||
Scenario: Resolve scope with conflicting flags raises BadParameter
|
Scenario: Resolve scope with conflicting flags raises BadParameter
|
||||||
When I resolve invariant add scope with --global and --project flags
|
When I resolve invariant scope with global and project flags
|
||||||
Then invariant add rejects conflicting scope flags
|
Then a BadParameter error should be raised for invariant scope
|
||||||
|
|
||||||
Scenario: List invariants rejects conflicting scope flags
|
# === _invariant_dict helper ===
|
||||||
When I resolve invariant list with conflicting scoped flags
|
|
||||||
Then list invariants rejects conflicting scope flags
|
Scenario: Invariant dict serializes an invariant correctly
|
||||||
|
Given a sample invariant with known fields for dict test
|
||||||
|
When I call invariant_dict on the sample invariant
|
||||||
|
Then the invariant dict should contain the correct id and text
|
||||||
|
And the invariant dict should contain scope and source_name
|
||||||
|
And the invariant dict should contain active and created_at ISO string
|
||||||
|
|
||||||
|
# === _get_service singleton ===
|
||||||
|
|
||||||
|
Scenario: Get service creates InvariantService lazily
|
||||||
|
When I call get_service with invariant module service reset to None
|
||||||
|
Then an InvariantService instance should be returned from get_service
|
||||||
|
And calling get_service again returns the same InvariantService instance
|
||||||
|
|
||||||
|
# === invariant add command ===
|
||||||
|
|
||||||
|
Scenario: Add invariant with --global flag via CLI
|
||||||
|
Given a mocked InvariantService for invariant CLI add
|
||||||
|
When I invoke invariant add with "--global" and text "Never delete prod data"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant CLI output should contain "Invariant added"
|
||||||
|
And the invariant CLI output should contain "Never delete prod data"
|
||||||
|
|
||||||
|
Scenario: Add invariant with --project flag via CLI
|
||||||
|
Given a mocked InvariantService for invariant CLI add
|
||||||
|
When I invoke invariant add with "--project myapp" and text "All APIs need tests"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant CLI output should contain "Invariant added"
|
||||||
|
|
||||||
|
Scenario: Add invariant without scope flag via CLI
|
||||||
|
Given a mocked InvariantService for invariant CLI add
|
||||||
|
When I invoke invariant add without scope flags and text "Missing scope flag"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant CLI output should contain "Invariant added"
|
||||||
|
|
||||||
|
Scenario: Add invariant with --format json via CLI
|
||||||
|
Given a mocked InvariantService for invariant CLI add
|
||||||
|
When I invoke invariant add with "--global --format json" and text "JSON constraint"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant CLI output should contain "JSON constraint"
|
||||||
|
|
||||||
|
Scenario: Add invariant handles CleverAgentsError
|
||||||
|
Given a mocked InvariantService that raises CleverAgentsError on add
|
||||||
|
When I invoke invariant add with "--global" and text "will fail"
|
||||||
|
Then the invariant CLI exit code should be non-zero
|
||||||
|
And the invariant CLI output should contain "Error"
|
||||||
|
|
||||||
|
# === invariant list command ===
|
||||||
|
|
||||||
|
Scenario: List invariants with no results
|
||||||
|
Given a mocked InvariantService that returns empty invariant list
|
||||||
|
When I invoke invariant list with no filters
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant CLI output should contain "No invariants found"
|
||||||
|
|
||||||
|
Scenario: List invariants with results in rich format
|
||||||
|
Given a mocked InvariantService that returns two invariants for list
|
||||||
|
When I invoke invariant list with no filters
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant CLI output should contain "Invariants"
|
||||||
|
And the invariant CLI output should contain "Never delete prod"
|
||||||
|
|
||||||
|
Scenario: List invariants with --global filter
|
||||||
|
Given a mocked InvariantService that returns two invariants for list
|
||||||
|
When I invoke invariant list with flags "--global"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant service list was called with global scope
|
||||||
|
|
||||||
|
Scenario: List invariants with --project filter
|
||||||
|
Given a mocked InvariantService that returns two invariants for list
|
||||||
|
When I invoke invariant list with flags "--project myapp"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant service list was called with project scope and source "myapp"
|
||||||
|
|
||||||
|
Scenario: List invariants with --plan filter
|
||||||
|
Given a mocked InvariantService that returns two invariants for list
|
||||||
|
When I invoke invariant list with flags "--plan PLAN_01HX"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant service list was called with plan scope and source "PLAN_01HX"
|
||||||
|
|
||||||
|
Scenario: List invariants with --action filter
|
||||||
|
Given a mocked InvariantService that returns two invariants for list
|
||||||
|
When I invoke invariant list with flags "--action deploy"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant service list was called with action scope and source "deploy"
|
||||||
|
|
||||||
|
Scenario: List invariants with --effective flag
|
||||||
|
Given a mocked InvariantService that returns two invariants for list
|
||||||
|
When I invoke invariant list with flags "--effective"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant service list was called with effective true
|
||||||
|
|
||||||
|
Scenario: List invariants with regex filter matching
|
||||||
|
Given a mocked InvariantService that returns two invariants for list
|
||||||
|
When I invoke invariant list with regex "prod"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant CLI output should contain "Never delete prod"
|
||||||
|
|
||||||
|
Scenario: List invariants with invalid regex
|
||||||
|
Given a mocked InvariantService that returns two invariants for list
|
||||||
|
When I invoke invariant list with regex "[invalid"
|
||||||
|
Then the invariant CLI exit code should be non-zero
|
||||||
|
And the invariant CLI output should contain "Invalid regex"
|
||||||
|
|
||||||
|
Scenario: List invariants with --format json
|
||||||
|
Given a mocked InvariantService that returns two invariants for list
|
||||||
|
When I invoke invariant list with flags "--format json"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant CLI output should contain "Never delete prod"
|
||||||
|
|
||||||
|
# === invariant remove command ===
|
||||||
|
|
||||||
|
Scenario: Remove invariant with --yes flag
|
||||||
|
Given a mocked InvariantService for invariant CLI remove
|
||||||
|
When I invoke invariant remove with "--yes" for id "INV_01HX"
|
||||||
|
Then the invariant CLI exit code should be 0
|
||||||
|
And the invariant CLI output should contain "Invariant removed"
|
||||||
|
|
||||||
|
Scenario: Remove invariant without --yes cancelled by user
|
||||||
|
Given a mocked InvariantService for invariant CLI remove
|
||||||
|
When I invoke invariant remove without --yes for id "INV_01HX" and answer no
|
||||||
|
Then the invariant CLI exit code should be non-zero
|
||||||
|
And the invariant CLI output should contain "Cancelled"
|
||||||
|
|
||||||
|
Scenario: Remove invariant raises NotFoundError
|
||||||
|
Given a mocked InvariantService that raises NotFoundError on remove
|
||||||
|
When I invoke invariant remove with "--yes" for id "MISSING_ID"
|
||||||
|
Then the invariant CLI exit code should be non-zero
|
||||||
|
And the invariant CLI output should contain "not found"
|
||||||
|
|||||||
@@ -1,124 +1,366 @@
|
|||||||
"""BDD step definitions for invariant CLI new coverage tests.
|
"""Step definitions for invariant_cli_new_coverage.feature.
|
||||||
|
|
||||||
This module provides Cucumber-style steps used in the BDD feature tests
|
Tests all CLI commands and helper functions in
|
||||||
for the ``invariant`` CLI command group, covering scope resolution and
|
cleveragents.cli.commands.invariant to bring coverage from 0% to high.
|
||||||
conflict detection via ``_resolve_scope()``.
|
|
||||||
|
|
||||||
Based on scenarios from ``features/invariant_cli_new_coverage.feature``.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
from datetime import datetime
|
||||||
from typing import Annotated
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
from behave import given, then, when # type: ignore[import-untyped]
|
from behave import given, then, when
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
from cleveragents.cli.commands.invariant import (
|
||||||
|
_get_service,
|
||||||
|
_invariant_dict,
|
||||||
|
_resolve_scope,
|
||||||
|
app,
|
||||||
|
)
|
||||||
|
from cleveragents.core.exceptions import CleverAgentsError, NotFoundError
|
||||||
|
from cleveragents.domain.models.core.invariant import Invariant, InvariantScope
|
||||||
|
|
||||||
|
_runner = CliRunner()
|
||||||
|
|
||||||
|
_ULID = "01JTEST0000000000000000001"
|
||||||
|
_ULID2 = "01JTEST0000000000000000002"
|
||||||
|
_NOW = datetime(2025, 7, 1, 12, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
@given("invariant service mock is configured")
|
def _make_invariant(
|
||||||
def step_service_mock(context):
|
*,
|
||||||
"""Prepare a mocked InvariantService for the scenario."""
|
inv_id: str = _ULID,
|
||||||
context.inv_service_mocked = True
|
text: str = "Never delete prod data",
|
||||||
|
scope: InvariantScope = InvariantScope.GLOBAL,
|
||||||
|
source_name: str = "system",
|
||||||
@given("a global scope invariant exists")
|
active: bool = True,
|
||||||
def step_global_invariant_exists(context):
|
created_at: datetime = _NOW,
|
||||||
"""Ensure a GLOBAL-scoped invariant is in the service store."""
|
) -> Invariant:
|
||||||
from cleveragents.domain.models.core.invariant import Invariant, InvariantScope
|
"""Build a test Invariant with sensible defaults."""
|
||||||
|
return Invariant(
|
||||||
context._test_inv_ids = []
|
id=inv_id,
|
||||||
context._test_invariants = [
|
text=text,
|
||||||
Invariant(
|
scope=scope,
|
||||||
id="01HZGLOBAL00000000000000A1",
|
source_name=source_name,
|
||||||
text="Never delete production data",
|
active=active,
|
||||||
scope=InvariantScope.GLOBAL,
|
created_at=created_at,
|
||||||
source_name="system",
|
|
||||||
active=True,
|
|
||||||
created_at=context._now_override(),
|
|
||||||
)
|
)
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
def _patch_svc(context, svc):
|
||||||
# _resolve_scope step definitions (add command)
|
"""Patch the module-level _service and register cleanup."""
|
||||||
# ---------------------------------------------------------------------------
|
patcher = patch("cleveragents.cli.commands.invariant._service", svc)
|
||||||
|
patcher.start()
|
||||||
|
context.add_cleanup(patcher.stop)
|
||||||
|
|
||||||
@when("I resolve invariant add scope with only global flag")
|
|
||||||
def step_resolve_global_only(context):
|
# ================================================================
|
||||||
context.inv_add_global_ok = False
|
# _resolve_scope helper steps
|
||||||
context.inv_add_global_scope = None
|
# ================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@when("I resolve invariant scope with global flag set")
|
||||||
|
def step_resolve_scope_global(context):
|
||||||
|
context.resolved_scope, context.resolved_source = _resolve_scope(
|
||||||
|
is_global=True, project=None, plan=None, action=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@when('I resolve invariant scope with project "{project}"')
|
||||||
|
def step_resolve_scope_project(context, project):
|
||||||
|
context.resolved_scope, context.resolved_source = _resolve_scope(
|
||||||
|
is_global=False, project=project, plan=None, action=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@when('I resolve invariant scope with plan "{plan}"')
|
||||||
|
def step_resolve_scope_plan(context, plan):
|
||||||
|
context.resolved_scope, context.resolved_source = _resolve_scope(
|
||||||
|
is_global=False, project=None, plan=plan, action=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@when('I resolve invariant scope with action "{action}"')
|
||||||
|
def step_resolve_scope_action(context, action):
|
||||||
|
context.resolved_scope, context.resolved_source = _resolve_scope(
|
||||||
|
is_global=False, project=None, plan=None, action=action
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@when("I resolve invariant scope with no flags")
|
||||||
|
def step_resolve_scope_default(context):
|
||||||
|
context.resolved_scope, context.resolved_source = _resolve_scope(
|
||||||
|
is_global=False, project=None, plan=None, action=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@when("I resolve invariant scope with global and project flags")
|
||||||
|
def step_resolve_scope_conflicting(context):
|
||||||
|
context.inv_bad_parameter_raised = False
|
||||||
try:
|
try:
|
||||||
from cleveragents.cli.commands.invariant import _resolve_scope, InvariantScope
|
|
||||||
scope, source_name = _resolve_scope(is_global=True, project=None, plan=None, action=None)
|
|
||||||
context.inv_add_global_ok = True
|
|
||||||
context.inv_add_global_scope = scope
|
|
||||||
context.inv_add_global_source = source_name
|
|
||||||
except Exception as exc:
|
|
||||||
context.inv_add_error = str(exc)
|
|
||||||
|
|
||||||
|
|
||||||
@when("I resolve invariant add scope with only project flag")
|
|
||||||
def step_resolve_project_only(context):
|
|
||||||
context.inv_add_project_ok = False
|
|
||||||
try:
|
|
||||||
from cleveragents.cli.commands.invariant import _resolve_scope, InvariantScope
|
|
||||||
scope, source_name = _resolve_scope(is_global=False, project="myapp", plan=None, action=None)
|
|
||||||
context.inv_add_project_ok = True
|
|
||||||
context.inv_add_project_scope = scope
|
|
||||||
context.inv_add_project_source = source_name
|
|
||||||
except Exception as exc:
|
|
||||||
context.inv_add_error = str(exc)
|
|
||||||
|
|
||||||
|
|
||||||
@when("I resolve invariant add scope with --global and --project flags")
|
|
||||||
def step_resolve_global_project(context):
|
|
||||||
context.inv_add_conflict_raised = False
|
|
||||||
try:
|
|
||||||
from cleveragents.cli.commands.invariant import _resolve_scope, InvariantScope
|
|
||||||
_resolve_scope(is_global=True, project="myapp", plan=None, action=None)
|
_resolve_scope(is_global=True, project="myapp", plan=None, action=None)
|
||||||
except typer.BadParameter:
|
except typer.BadParameter:
|
||||||
context.inv_add_conflict_raised = True
|
context.inv_bad_parameter_raised = True
|
||||||
|
|
||||||
|
|
||||||
@then("invariant add with global flag returns GLOBAL scope")
|
@then('the resolved invariant scope should be "{scope}"')
|
||||||
def step_check_global_scope(context):
|
def step_check_resolved_scope(context, scope):
|
||||||
assert context.inv_add_global_ok, "Expected _resolve_scope to succeed for --global"
|
assert context.resolved_scope.value == scope, (
|
||||||
from cleveragents.domain.models.core.invariant import InvariantScope
|
f"Expected scope '{scope}', got '{context.resolved_scope.value}'"
|
||||||
|
|
||||||
assert context.inv_add_global_scope == InvariantScope.GLOBAL
|
|
||||||
assert context.inv_add_global_source == "system"
|
|
||||||
|
|
||||||
|
|
||||||
@then("invariant add with project flag returns PROJECT scope")
|
|
||||||
def step_check_project_scope(context):
|
|
||||||
assert context.inv_add_project_ok, "Expected _resolve_scope to succeed for --project"
|
|
||||||
from cleveragents.domain.models.core.invariant import InvariantScope
|
|
||||||
|
|
||||||
assert context.inv_add_project_scope == InvariantScope.PROJECT
|
|
||||||
assert context.inv_add_project_source == "myapp"
|
|
||||||
|
|
||||||
|
|
||||||
@then("invariant add rejects conflicting scope flags")
|
|
||||||
def step_check_add_conflicting(context):
|
|
||||||
assert context.inv_add_conflict_raised, (
|
|
||||||
"Expected BadParameter for invariant add with conflicting scopes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# === list_invariants scope conflict detection ===
|
@then('the resolved invariant source name should be "{source}"')
|
||||||
|
def step_check_resolved_source(context, source):
|
||||||
@when("I resolve invariant list with conflicting scoped flags")
|
assert context.resolved_source == source, (
|
||||||
def step_resolve_list_conflicting(context):
|
f"Expected source '{source}', got '{context.resolved_source}'"
|
||||||
context.inv_list_bad_parameter_raised = False
|
|
||||||
try:
|
|
||||||
from cleveragents.cli.commands.invariant import _resolve_scope, InvariantScope
|
|
||||||
_resolve_scope(is_global=True, project="myapp", plan=None, action=None)
|
|
||||||
except typer.BadParameter:
|
|
||||||
context.inv_list_bad_parameter_raised = True
|
|
||||||
|
|
||||||
|
|
||||||
@then("list invariants rejects conflicting scope flags")
|
|
||||||
def step_check_list_conflicting(context):
|
|
||||||
assert context.inv_list_bad_parameter_raised, (
|
|
||||||
"Expected BadParameter for list_invariants with conflicting scopes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@then("a BadParameter error should be raised for invariant scope")
|
||||||
|
def step_check_bad_parameter(context):
|
||||||
|
assert context.inv_bad_parameter_raised, "Expected typer.BadParameter to be raised"
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# _invariant_dict helper steps
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@given("a sample invariant with known fields for dict test")
|
||||||
|
def step_create_sample_invariant(context):
|
||||||
|
context.sample_inv = _make_invariant()
|
||||||
|
|
||||||
|
|
||||||
|
@when("I call invariant_dict on the sample invariant")
|
||||||
|
def step_call_invariant_dict(context):
|
||||||
|
context.inv_dict = _invariant_dict(context.sample_inv)
|
||||||
|
|
||||||
|
|
||||||
|
@then("the invariant dict should contain the correct id and text")
|
||||||
|
def step_check_dict_id_text(context):
|
||||||
|
d = context.inv_dict
|
||||||
|
assert d["id"] == _ULID
|
||||||
|
assert d["text"] == "Never delete prod data"
|
||||||
|
|
||||||
|
|
||||||
|
@then("the invariant dict should contain scope and source_name")
|
||||||
|
def step_check_dict_scope_source(context):
|
||||||
|
d = context.inv_dict
|
||||||
|
assert d["scope"] == "global"
|
||||||
|
assert d["source_name"] == "system"
|
||||||
|
|
||||||
|
|
||||||
|
@then("the invariant dict should contain active and created_at ISO string")
|
||||||
|
def step_check_dict_active_created(context):
|
||||||
|
d = context.inv_dict
|
||||||
|
assert d["active"] is True
|
||||||
|
assert d["created_at"] == _NOW.isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# _get_service singleton steps
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@when("I call get_service with invariant module service reset to None")
|
||||||
|
def step_get_service_reset(context):
|
||||||
|
import cleveragents.cli.commands.invariant as mod
|
||||||
|
|
||||||
|
# Save original and reset
|
||||||
|
context._orig_inv_service = mod._service
|
||||||
|
mod._service = None
|
||||||
|
context.add_cleanup(lambda: setattr(mod, "_service", context._orig_inv_service))
|
||||||
|
|
||||||
|
context.first_inv_service = _get_service()
|
||||||
|
|
||||||
|
|
||||||
|
@then("an InvariantService instance should be returned from get_service")
|
||||||
|
def step_check_service_instance(context):
|
||||||
|
from cleveragents.application.services.invariant_service import InvariantService
|
||||||
|
|
||||||
|
assert isinstance(context.first_inv_service, InvariantService)
|
||||||
|
|
||||||
|
|
||||||
|
@then("calling get_service again returns the same InvariantService instance")
|
||||||
|
def step_check_service_singleton(context):
|
||||||
|
second = _get_service()
|
||||||
|
assert second is context.first_inv_service
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# invariant add command steps
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@given("a mocked InvariantService for invariant CLI add")
|
||||||
|
def step_mock_service_for_add(context):
|
||||||
|
svc = MagicMock()
|
||||||
|
|
||||||
|
def _fake_add(text, scope, source_name):
|
||||||
|
return _make_invariant(text=text, scope=scope, source_name=source_name)
|
||||||
|
|
||||||
|
svc.add_invariant.side_effect = _fake_add
|
||||||
|
svc.list_invariants.return_value = []
|
||||||
|
context.inv_mock_svc = svc
|
||||||
|
_patch_svc(context, svc)
|
||||||
|
|
||||||
|
|
||||||
|
@when('I invoke invariant add with "{flags}" and text "{text}"')
|
||||||
|
def step_run_add(context, flags, text):
|
||||||
|
args = ["add", *flags.split(), text]
|
||||||
|
context.inv_result = _runner.invoke(app, args)
|
||||||
|
|
||||||
|
|
||||||
|
@when('I invoke invariant add without scope flags and text "{text}"')
|
||||||
|
def step_run_add_no_flags(context, text):
|
||||||
|
context.inv_result = _runner.invoke(app, ["add", text])
|
||||||
|
|
||||||
|
|
||||||
|
@then("the invariant CLI exit code should be 0")
|
||||||
|
def step_check_exit_zero(context):
|
||||||
|
assert context.inv_result.exit_code == 0, (
|
||||||
|
f"Expected exit code 0, got {context.inv_result.exit_code}.\n"
|
||||||
|
f"Output: {context.inv_result.output}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@then("the invariant CLI exit code should be non-zero")
|
||||||
|
def step_check_exit_nonzero(context):
|
||||||
|
assert context.inv_result.exit_code != 0, (
|
||||||
|
f"Expected non-zero exit code, got {context.inv_result.exit_code}.\n"
|
||||||
|
f"Output: {context.inv_result.output}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@then('the invariant CLI output should contain "{text}"')
|
||||||
|
def step_check_output_contains(context, text):
|
||||||
|
assert text in context.inv_result.output, (
|
||||||
|
f"Expected '{text}' in output, got:\n{context.inv_result.output}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@given("a mocked InvariantService that raises CleverAgentsError on add")
|
||||||
|
def step_mock_service_error_add(context):
|
||||||
|
svc = MagicMock()
|
||||||
|
svc.add_invariant.side_effect = CleverAgentsError("Service failure")
|
||||||
|
_patch_svc(context, svc)
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# invariant list command steps
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@given("a mocked InvariantService that returns empty invariant list")
|
||||||
|
def step_mock_service_empty_list(context):
|
||||||
|
svc = MagicMock()
|
||||||
|
svc.list_invariants.return_value = []
|
||||||
|
context.inv_mock_svc = svc
|
||||||
|
_patch_svc(context, svc)
|
||||||
|
|
||||||
|
|
||||||
|
@given("a mocked InvariantService that returns two invariants for list")
|
||||||
|
def step_mock_service_two_invariants(context):
|
||||||
|
svc = MagicMock()
|
||||||
|
inv1 = _make_invariant(inv_id=_ULID, text="Never delete prod data")
|
||||||
|
inv2 = _make_invariant(
|
||||||
|
inv_id=_ULID2,
|
||||||
|
text="All changes need review",
|
||||||
|
scope=InvariantScope.PROJECT,
|
||||||
|
source_name="myapp",
|
||||||
|
)
|
||||||
|
svc.list_invariants.return_value = [inv1, inv2]
|
||||||
|
context.inv_mock_svc = svc
|
||||||
|
_patch_svc(context, svc)
|
||||||
|
|
||||||
|
|
||||||
|
@when("I invoke invariant list with no filters")
|
||||||
|
def step_run_list_no_filters(context):
|
||||||
|
context.inv_result = _runner.invoke(app, ["list"])
|
||||||
|
|
||||||
|
|
||||||
|
@when('I invoke invariant list with flags "{flags}"')
|
||||||
|
def step_run_list_with_flags(context, flags):
|
||||||
|
args = ["list", *flags.split()]
|
||||||
|
context.inv_result = _runner.invoke(app, args)
|
||||||
|
|
||||||
|
|
||||||
|
@when('I invoke invariant list with regex "{pattern}"')
|
||||||
|
def step_run_list_with_regex(context, pattern):
|
||||||
|
context.inv_result = _runner.invoke(app, ["list", pattern])
|
||||||
|
|
||||||
|
|
||||||
|
@then("the invariant service list was called with global scope")
|
||||||
|
def step_check_list_global(context):
|
||||||
|
call_kwargs = context.inv_mock_svc.list_invariants.call_args
|
||||||
|
assert call_kwargs is not None
|
||||||
|
kwargs = call_kwargs[1] if call_kwargs[1] else {}
|
||||||
|
assert kwargs.get("scope") == InvariantScope.GLOBAL
|
||||||
|
|
||||||
|
|
||||||
|
@then('the invariant service list was called with project scope and source "{source}"')
|
||||||
|
def step_check_list_project(context, source):
|
||||||
|
call_kwargs = context.inv_mock_svc.list_invariants.call_args
|
||||||
|
kwargs = call_kwargs[1] if call_kwargs[1] else {}
|
||||||
|
assert kwargs.get("scope") == InvariantScope.PROJECT
|
||||||
|
assert kwargs.get("source_name") == source
|
||||||
|
|
||||||
|
|
||||||
|
@then('the invariant service list was called with plan scope and source "{source}"')
|
||||||
|
def step_check_list_plan(context, source):
|
||||||
|
call_kwargs = context.inv_mock_svc.list_invariants.call_args
|
||||||
|
kwargs = call_kwargs[1] if call_kwargs[1] else {}
|
||||||
|
assert kwargs.get("scope") == InvariantScope.PLAN
|
||||||
|
assert kwargs.get("source_name") == source
|
||||||
|
|
||||||
|
|
||||||
|
@then('the invariant service list was called with action scope and source "{source}"')
|
||||||
|
def step_check_list_action(context, source):
|
||||||
|
call_kwargs = context.inv_mock_svc.list_invariants.call_args
|
||||||
|
kwargs = call_kwargs[1] if call_kwargs[1] else {}
|
||||||
|
assert kwargs.get("scope") == InvariantScope.ACTION
|
||||||
|
assert kwargs.get("source_name") == source
|
||||||
|
|
||||||
|
|
||||||
|
@then("the invariant service list was called with effective true")
|
||||||
|
def step_check_list_effective(context):
|
||||||
|
call_kwargs = context.inv_mock_svc.list_invariants.call_args
|
||||||
|
kwargs = call_kwargs[1] if call_kwargs[1] else {}
|
||||||
|
assert kwargs.get("effective") is True
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# invariant remove command steps
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@given("a mocked InvariantService for invariant CLI remove")
|
||||||
|
def step_mock_service_for_remove(context):
|
||||||
|
svc = MagicMock()
|
||||||
|
mock_inv = _make_invariant(active=False)
|
||||||
|
svc.remove_invariant.return_value = mock_inv
|
||||||
|
context.inv_mock_svc = svc
|
||||||
|
_patch_svc(context, svc)
|
||||||
|
|
||||||
|
|
||||||
|
@when('I invoke invariant remove with "{flags}" for id "{inv_id}"')
|
||||||
|
def step_run_remove_with_flags(context, flags, inv_id):
|
||||||
|
args = ["remove", *flags.split(), inv_id]
|
||||||
|
context.inv_result = _runner.invoke(app, args)
|
||||||
|
|
||||||
|
|
||||||
|
@when('I invoke invariant remove without --yes for id "{inv_id}" and answer no')
|
||||||
|
def step_run_remove_no_confirm(context, inv_id):
|
||||||
|
context.inv_result = _runner.invoke(app, ["remove", inv_id], input="n\n")
|
||||||
|
|
||||||
|
|
||||||
|
@given("a mocked InvariantService that raises NotFoundError on remove")
|
||||||
|
def step_mock_service_not_found_remove(context):
|
||||||
|
svc = MagicMock()
|
||||||
|
svc.remove_invariant.side_effect = NotFoundError(
|
||||||
|
resource_type="invariant", resource_id="MISSING_ID"
|
||||||
|
)
|
||||||
|
_patch_svc(context, svc)
|
||||||
|
|||||||
+154
-45
@@ -1,90 +1,199 @@
|
|||||||
"""Robot Framework helper: invariant CLI smoke tests via ``invariant_app``.
|
"""Helper script for invariant_cli.robot smoke tests.
|
||||||
|
|
||||||
This module wraps the Typer ``invariant_app`` with ``cli_runner.invoke`` and
|
Each subcommand is a self-contained check that prints a sentinel on success.
|
||||||
provides a set of callable functions keyed by command name in ``COMMANDS``.
|
|
||||||
|
|
||||||
Usage::
|
|
||||||
|
|
||||||
python robot/helper_invariant_cli.py scope-conflict
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from unittest.mock import patch, MagicMock
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from typer.testing import CliRunner
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
||||||
|
if _SRC not in sys.path:
|
||||||
|
sys.path.insert(0, _SRC)
|
||||||
|
|
||||||
from cleveragents.cli.commands.invariant import invariant_app
|
from typer.testing import CliRunner # noqa: E402
|
||||||
|
|
||||||
|
from cleveragents.application.services.invariant_service import ( # noqa: E402
|
||||||
|
InvariantService,
|
||||||
|
)
|
||||||
|
from cleveragents.cli.commands.invariant import app as invariant_app # noqa: E402
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
|
|
||||||
|
|
||||||
def _fresh_service() -> MagicMock:
|
def _fresh_service() -> InvariantService:
|
||||||
"""Return a mock InvariantService that always gives an empty list."""
|
"""Create a fresh in-memory service for each test."""
|
||||||
svc = MagicMock()
|
return InvariantService()
|
||||||
svc.list_invariants.return_value = []
|
|
||||||
svc.add_invariant.side_effect = Exception("Mocked – add not tested here")
|
|
||||||
return svc
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Smoke-test functions
|
# Subcommands
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def scope_conflict() -> None:
|
|
||||||
"""Verify that ``invariant add --global --project`` rejects conflicting scopes."""
|
def add_global() -> None:
|
||||||
svc = _fresh_service()
|
svc = _fresh_service()
|
||||||
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
invariant_app,
|
invariant_app, ["add", "--global", "Never delete production data"]
|
||||||
["add", "--global", "--project", "myapp", "Some constraint"],
|
|
||||||
)
|
)
|
||||||
if result.exit_code != 0:
|
if result.exit_code == 0 and "Invariant added" in result.stdout:
|
||||||
print("invariant-add-scope-conflict-ok")
|
print("invariant-add-global-ok")
|
||||||
else:
|
else:
|
||||||
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def list_scope_conflict() -> None:
|
def add_project() -> None:
|
||||||
|
svc = _fresh_service()
|
||||||
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
|
result = runner.invoke(
|
||||||
|
invariant_app, ["add", "--project", "myapp", "All API changes need tests"]
|
||||||
|
)
|
||||||
|
if result.exit_code == 0 and "Invariant added" in result.stdout:
|
||||||
|
print("invariant-add-project-ok")
|
||||||
|
else:
|
||||||
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def add_plan() -> None:
|
||||||
svc = _fresh_service()
|
svc = _fresh_service()
|
||||||
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
invariant_app,
|
invariant_app,
|
||||||
["list", "--global", "--project", "myapp"],
|
["add", "--plan", "01TESTPLANID00000000000000", "Use Python 3.13"],
|
||||||
|
)
|
||||||
|
if result.exit_code == 0 and "Invariant added" in result.stdout:
|
||||||
|
print("invariant-add-plan-ok")
|
||||||
|
else:
|
||||||
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def add_action() -> None:
|
||||||
|
svc = _fresh_service()
|
||||||
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
|
result = runner.invoke(
|
||||||
|
invariant_app,
|
||||||
|
["add", "--action", "local/code-coverage", "Min 80% coverage"],
|
||||||
|
)
|
||||||
|
if result.exit_code == 0 and "Invariant added" in result.stdout:
|
||||||
|
print("invariant-add-action-ok")
|
||||||
|
else:
|
||||||
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def list_empty() -> None:
|
||||||
|
svc = _fresh_service()
|
||||||
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
|
result = runner.invoke(invariant_app, ["list"])
|
||||||
|
if result.exit_code == 0 and "No invariants found" in result.stdout:
|
||||||
|
print("invariant-list-empty-ok")
|
||||||
|
else:
|
||||||
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def list_filter() -> None:
|
||||||
|
svc = _fresh_service()
|
||||||
|
svc.add_invariant("Global rule", scope=_scope("global"), source_name="system")
|
||||||
|
svc.add_invariant("Project rule", scope=_scope("project"), source_name="myapp")
|
||||||
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
|
result = runner.invoke(invariant_app, ["list", "--global"])
|
||||||
|
if result.exit_code == 0 and "Global rule" in result.stdout:
|
||||||
|
print("invariant-list-filter-ok")
|
||||||
|
else:
|
||||||
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def remove() -> None:
|
||||||
|
svc = _fresh_service()
|
||||||
|
inv = svc.add_invariant(
|
||||||
|
"To be removed", scope=_scope("global"), source_name="system"
|
||||||
|
)
|
||||||
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
|
result = runner.invoke(invariant_app, ["remove", "--yes", inv.id])
|
||||||
|
if result.exit_code == 0 and "Invariant removed" in result.stdout:
|
||||||
|
print("invariant-remove-ok")
|
||||||
|
else:
|
||||||
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def scope_conflict() -> None:
|
||||||
|
svc = _fresh_service()
|
||||||
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
|
result = runner.invoke(
|
||||||
|
invariant_app,
|
||||||
|
["add", "--global", "--project", "myapp", "Conflicting scopes"],
|
||||||
)
|
)
|
||||||
# Should fail with a bad parameter error
|
# Should fail with a bad parameter error
|
||||||
if result.exit_code != 0:
|
if result.exit_code != 0:
|
||||||
print("invariant-list-scope-conflict-ok")
|
print("invariant-scope-conflict-ok")
|
||||||
else:
|
else:
|
||||||
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def add_no_scope() -> None:
|
||||||
|
svc = _fresh_service()
|
||||||
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
|
result = runner.invoke(invariant_app, ["add", "Missing scope invariant"])
|
||||||
|
output = result.output or ""
|
||||||
|
if result.exit_code == 0 and "Invariant added" in output:
|
||||||
|
print("invariant-add-no-scope-global-ok")
|
||||||
|
else:
|
||||||
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def list_json() -> None:
|
||||||
|
svc = _fresh_service()
|
||||||
|
svc.add_invariant("JSON test rule", scope=_scope("global"), source_name="system")
|
||||||
|
with patch("cleveragents.cli.commands.invariant._get_service", return_value=svc):
|
||||||
|
result = runner.invoke(invariant_app, ["list", "--format", "json"])
|
||||||
|
if result.exit_code == 0:
|
||||||
|
# Verify output is valid JSON-like
|
||||||
|
output = result.stdout.strip()
|
||||||
|
if "JSON test rule" in output:
|
||||||
|
print("invariant-list-json-ok")
|
||||||
|
return
|
||||||
|
print(f"FAIL: exit={result.exit_code} out={result.stdout}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def _scope(name: str):
|
||||||
|
from cleveragents.domain.models.core.invariant import InvariantScope
|
||||||
|
|
||||||
|
return InvariantScope(name)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Command registry
|
# Dispatcher
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
COMMANDS = {
|
COMMANDS = {
|
||||||
|
"add-global": add_global,
|
||||||
|
"add-project": add_project,
|
||||||
|
"add-plan": add_plan,
|
||||||
|
"add-action": add_action,
|
||||||
|
"list-empty": list_empty,
|
||||||
|
"list-filter": list_filter,
|
||||||
|
"remove": remove,
|
||||||
"scope-conflict": scope_conflict,
|
"scope-conflict": scope_conflict,
|
||||||
"list-scope-conflict": list_scope_conflict,
|
"add-no-scope": add_no_scope,
|
||||||
|
"list-json": list_json,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
"""Dispatch to the requested helper function."""
|
|
||||||
if len(sys.argv) < 2 :
|
|
||||||
print(f"Usage: python {__file__} <command>", file=sys.stderr)
|
|
||||||
print(f"Available commands: {', '.join(COMMANDS)}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
cmd_name = sys.argv[1]
|
|
||||||
if cmd_name not in COMMANDS:
|
|
||||||
print(f"Unknown command: {cmd_name}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
COMMANDS[cmd_name]()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
cmd = sys.argv[1] if len(sys.argv) > 1 else None
|
||||||
|
if cmd not in COMMANDS:
|
||||||
|
print(f"Unknown command: {cmd}", file=sys.stderr)
|
||||||
|
print(f"Available: {', '.join(sorted(COMMANDS))}", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
COMMANDS[cmd]()
|
||||||
|
|||||||
+69
-10
@@ -1,12 +1,71 @@
|
|||||||
*** Test Cases ***
|
*** Settings ***
|
||||||
Invariant Add Scope Conflict Rejected
|
Documentation Smoke tests for Invariant CLI commands
|
||||||
[Documentation] Verify that ``invariant add --global --project`` rejects conflicting scopes
|
Resource ${CURDIR}/common.resource
|
||||||
${result}= Run Process python${/}${WORKSPACE}/robot/helper_invariant_cli.py scope-conflict cwd=${WORKSPACE}
|
Suite Setup Setup Test Environment
|
||||||
Should Be Equal As Integers ${result.rc} 0
|
Suite Teardown Cleanup Test Environment
|
||||||
Should Contain ${result.stdout} invariant-add-scope-conflict-ok
|
|
||||||
|
|
||||||
Invariant List Scope Conflict Rejected
|
*** Variables ***
|
||||||
[Documentation] Verify that ``invariant list --global --project`` rejects conflicting scopes
|
${HELPER} ${CURDIR}/helper_invariant_cli.py
|
||||||
${result}= Run Process python${/}${WORKSPACE}/robot/helper_invariant_cli.py list-scope-conflict cwd=${WORKSPACE}
|
|
||||||
|
*** Test Cases ***
|
||||||
|
Invariant Add Global
|
||||||
|
[Documentation] Verify that ``invariant add --global`` creates a global invariant
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} add-global cwd=${WORKSPACE}
|
||||||
|
Log ${result.stdout}
|
||||||
|
Log ${result.stderr}
|
||||||
Should Be Equal As Integers ${result.rc} 0
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
Should Contain ${result.stdout} invariant-list-scope-conflict-ok
|
Should Contain ${result.stdout} invariant-add-global-ok
|
||||||
|
|
||||||
|
Invariant Add Project
|
||||||
|
[Documentation] Verify that ``invariant add --project`` creates a project invariant
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} add-project cwd=${WORKSPACE}
|
||||||
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
|
Should Contain ${result.stdout} invariant-add-project-ok
|
||||||
|
|
||||||
|
Invariant Add Plan
|
||||||
|
[Documentation] Verify that ``invariant add --plan`` creates a plan invariant
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} add-plan cwd=${WORKSPACE}
|
||||||
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
|
Should Contain ${result.stdout} invariant-add-plan-ok
|
||||||
|
|
||||||
|
Invariant Add Action
|
||||||
|
[Documentation] Verify that ``invariant add --action`` creates an action invariant
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} add-action cwd=${WORKSPACE}
|
||||||
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
|
Should Contain ${result.stdout} invariant-add-action-ok
|
||||||
|
|
||||||
|
Invariant List Empty
|
||||||
|
[Documentation] Verify that ``invariant list`` works when no invariants exist
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} list-empty cwd=${WORKSPACE}
|
||||||
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
|
Should Contain ${result.stdout} invariant-list-empty-ok
|
||||||
|
|
||||||
|
Invariant List With Filter
|
||||||
|
[Documentation] Verify that ``invariant list --global`` filters by scope
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} list-filter cwd=${WORKSPACE}
|
||||||
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
|
Should Contain ${result.stdout} invariant-list-filter-ok
|
||||||
|
|
||||||
|
Invariant Remove
|
||||||
|
[Documentation] Verify that ``invariant remove --yes`` soft-deletes
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} remove cwd=${WORKSPACE}
|
||||||
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
|
Should Contain ${result.stdout} invariant-remove-ok
|
||||||
|
|
||||||
|
Invariant Scope Conflict Rejected
|
||||||
|
[Documentation] Verify that conflicting scope flags are rejected
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} scope-conflict cwd=${WORKSPACE}
|
||||||
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
|
Should Contain ${result.stdout} invariant-scope-conflict-ok
|
||||||
|
|
||||||
|
Invariant Add Missing Scope Defaults Global
|
||||||
|
[Documentation] Verify that ``invariant add`` defaults to global when no scope flag is provided
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} add-no-scope cwd=${WORKSPACE}
|
||||||
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
|
Should Contain ${result.stdout} invariant-add-no-scope-global-ok
|
||||||
|
|
||||||
|
Invariant List JSON Format
|
||||||
|
[Documentation] Verify that ``invariant list --format json`` outputs JSON
|
||||||
|
${result}= Run Process ${PYTHON} ${HELPER} list-json cwd=${WORKSPACE}
|
||||||
|
Should Be Equal As Integers ${result.rc} 0
|
||||||
|
Should Contain ${result.stdout} invariant-list-json-ok
|
||||||
|
|||||||
Reference in New Issue
Block a user