051ee7c290
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 21s
CI / quality (pull_request) Successful in 31s
CI / typecheck (pull_request) Successful in 47s
CI / security (pull_request) Successful in 52s
CI / build (pull_request) Successful in 56s
CI / e2e_tests (pull_request) Successful in 5m1s
CI / integration_tests (pull_request) Successful in 5m30s
CI / unit_tests (pull_request) Successful in 5m42s
CI / docker (pull_request) Successful in 58s
CI / coverage (pull_request) Successful in 7m35s
CI / build (push) Successful in 21s
CI / docker (push) Has been skipped
CI / benchmark-regression (pull_request) Failing after 49m24s
CI / lint (push) Successful in 22s
CI / quality (push) Successful in 39s
CI / security (push) Successful in 48s
CI / typecheck (push) Successful in 1m26s
CI / benchmark-regression (push) Has been skipped
CI / e2e_tests (push) Successful in 5m53s
CI / coverage (push) Successful in 9m4s
CI / benchmark-publish (push) Successful in 19m10s
CI / integration_tests (push) Failing after 19m18s
CI / unit_tests (push) Failing after 19m20s
Added 52 new .feature files and corresponding _steps.py files targeting previously uncovered code paths in the following areas: - TUI layer: app, commands, persona (state/schema/registry), widgets, input (shell_exec, reference_parser) - Application services: plan lifecycle/service/executor, session, project, repo indexing, correction, checkpoint, actor, llm_actors, strategy coordinator, resource file watcher, service retry wiring - CLI commands: session, resource, repl, plan, db, automation_profile - Domain models: retry_policy, resource_type, cost_budget, docker_compose_analyzer, detail_level, _sql_string_aware, _postgresql_helpers - Core: circuit_breaker, retry_service_patterns - Infrastructure: repositories, transaction_sandbox, strategy_registry, plugins/loader, container - Config: settings - Agents: plan_generation, context_analysis, auto_debug - A2A: facade All new tests follow the Behave/Gherkin BDD standard. Resolved step definition collisions with unique prefixes. Fixed Alembic fileConfig logger disabling issue (disable_existing_loggers=False). ISSUES CLOSED: #1068
310 lines
12 KiB
Gherkin
310 lines
12 KiB
Gherkin
Feature: REPL module full coverage
|
|
Comprehensive unit-level tests that exercise every function and branch
|
|
inside ``src/cleveragents/cli/commands/repl.py`` to bring its coverage
|
|
from 0% to near-100%.
|
|
|
|
# -----------------------------------------------------------------
|
|
# Constants
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: Module constants are defined correctly
|
|
Given replcov the repl module is imported
|
|
Then the constant _MULTILINE_CONTINUATION equals backslash
|
|
And the constant _LAST_COMMAND_TOKEN equals "!!"
|
|
And the _REPL_COMMANDS list is non-empty
|
|
And the _BUILTIN_COMMANDS list contains help exit and quit
|
|
|
|
# -----------------------------------------------------------------
|
|
# _get_prompt_context
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: _get_prompt_context returns bare prompt when no env vars set
|
|
Given neither CLEVERAGENTS_PROJECT nor CLEVERAGENTS_PLAN is set
|
|
When I call _get_prompt_context
|
|
Then the prompt result is "agents> "
|
|
|
|
Scenario: _get_prompt_context returns project-only prompt
|
|
Given CLEVERAGENTS_PROJECT is set to "proj1" and CLEVERAGENTS_PLAN is unset
|
|
When I call _get_prompt_context
|
|
Then the prompt result is "agents (proj1)> "
|
|
|
|
Scenario: _get_prompt_context returns project+plan prompt
|
|
Given CLEVERAGENTS_PROJECT is set to "proj1" and CLEVERAGENTS_PLAN is set to "plan1"
|
|
When I call _get_prompt_context
|
|
Then the prompt result is "agents (proj1/plan1)> "
|
|
|
|
# -----------------------------------------------------------------
|
|
# _setup_history
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: _setup_history loads existing history file
|
|
Given a temporary history file that exists
|
|
When I call _setup_history with that path
|
|
Then readline.read_history_file is called with the path
|
|
And readline.set_history_length is called with 10000
|
|
|
|
Scenario: _setup_history skips read when file does not exist
|
|
Given a temporary history path that does not exist
|
|
When I call _setup_history with that path
|
|
Then readline.read_history_file is not called
|
|
And readline.set_history_length is called with 10000
|
|
|
|
# -----------------------------------------------------------------
|
|
# _save_history
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: _save_history writes the history file
|
|
Given a temporary history path for saving
|
|
When I call _save_history with that path
|
|
Then readline.write_history_file is called with the path
|
|
|
|
Scenario: _save_history suppresses OSError
|
|
Given readline.write_history_file will raise OSError
|
|
When I call _save_history with a dummy path
|
|
Then no exception is raised
|
|
|
|
# -----------------------------------------------------------------
|
|
# _setup_completer
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: _setup_completer installs readline completer
|
|
When I call _setup_completer
|
|
Then readline.set_completer is called with a callable
|
|
And readline.set_completer_delims is called
|
|
And readline.parse_and_bind is called with tab complete
|
|
|
|
Scenario: The installed completer returns matching tokens
|
|
Given the completer is installed via _setup_completer
|
|
When I query the completer with text "ver" and state 0
|
|
Then the completer returns "version"
|
|
|
|
Scenario: The installed completer returns None for out-of-range state
|
|
Given the completer is installed via _setup_completer
|
|
When I query the completer with text "ver" and state 99
|
|
Then the completer returns None
|
|
|
|
# -----------------------------------------------------------------
|
|
# _read_multiline
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: _read_multiline reads continuation lines ending normally
|
|
Given builtins.input will return "continued line"
|
|
When I call _read_multiline with initial line ending in backslash
|
|
Then the multiline result is "first continued line"
|
|
|
|
Scenario: _read_multiline handles chained continuations
|
|
Given builtins.input will return chained continuations
|
|
When I call _read_multiline with initial line ending in backslash
|
|
Then the multiline result is "first second third"
|
|
|
|
Scenario: _read_multiline handles EOFError in continuation
|
|
Given builtins.input will raise EOFError on continuation
|
|
When I call _read_multiline with initial line ending in backslash
|
|
Then the multiline result is "only"
|
|
|
|
Scenario: _read_multiline handles KeyboardInterrupt in continuation
|
|
Given builtins.input will raise KeyboardInterrupt on continuation
|
|
When I call _read_multiline with initial line ending in backslash
|
|
Then the multiline result is "only"
|
|
|
|
# -----------------------------------------------------------------
|
|
# _print_repl_help
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: _print_repl_help runs without error
|
|
When I call _print_repl_help
|
|
Then no exception is raised from _print_repl_help
|
|
|
|
# -----------------------------------------------------------------
|
|
# dispatch_command
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: dispatch_command returns 0 on success
|
|
Given the root Typer app is mocked to succeed
|
|
When I call dispatch_command with args "version"
|
|
Then the dispatch return code is 0
|
|
|
|
Scenario: dispatch_command returns code from SystemExit
|
|
Given the root Typer app raises SystemExit with code 42
|
|
When I call dispatch_command with args "bad"
|
|
Then the dispatch return code is 42
|
|
|
|
Scenario: dispatch_command returns 0 for SystemExit with None code
|
|
Given the root Typer app raises SystemExit with None code
|
|
When I call dispatch_command with args "something"
|
|
Then the dispatch return code is 0
|
|
|
|
Scenario: dispatch_command returns 130 on KeyboardInterrupt
|
|
Given the root Typer app raises KeyboardInterrupt
|
|
When I call dispatch_command with args "slow"
|
|
Then the dispatch return code is 130
|
|
|
|
Scenario: dispatch_command returns 1 and prints error on generic Exception
|
|
Given the root Typer app raises RuntimeError boom
|
|
When I call dispatch_command with args "fail"
|
|
Then the dispatch return code is 1
|
|
And the dispatch error output contains "boom"
|
|
|
|
# -----------------------------------------------------------------
|
|
# run_repl — exit commands
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: run_repl exits on :exit command
|
|
Given the REPL input sequence is
|
|
| line |
|
|
| :exit |
|
|
When I run the REPL loop
|
|
Then the REPL loop exit code is 0
|
|
|
|
Scenario: run_repl exits on :quit command
|
|
Given the REPL input sequence is
|
|
| line |
|
|
| :quit |
|
|
When I run the REPL loop
|
|
Then the REPL loop exit code is 0
|
|
|
|
# -----------------------------------------------------------------
|
|
# run_repl — help
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: run_repl handles :help and continues
|
|
Given the REPL input sequence is
|
|
| line |
|
|
| :help |
|
|
| :exit |
|
|
When I run the REPL loop
|
|
Then the REPL loop output contains "Built-in commands"
|
|
|
|
# -----------------------------------------------------------------
|
|
# run_repl — !! repeat
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: run_repl repeats last command with !!
|
|
Given the REPL input sequence is
|
|
| line |
|
|
| version |
|
|
| !! |
|
|
| :exit |
|
|
And dispatch_command is mocked for run_repl
|
|
When I run the REPL loop
|
|
Then dispatch_command was called at least 2 times with "version"
|
|
|
|
Scenario: run_repl warns when !! has no previous command
|
|
Given the REPL input sequence is
|
|
| line |
|
|
| !! |
|
|
| :exit |
|
|
When I run the REPL loop
|
|
Then the REPL loop output contains "No previous command"
|
|
|
|
# -----------------------------------------------------------------
|
|
# run_repl — empty and whitespace input
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: run_repl skips empty lines
|
|
Given the REPL input sequence is
|
|
| line |
|
|
| |
|
|
| :exit |
|
|
When I run the REPL loop
|
|
Then the REPL loop exit code is 0
|
|
|
|
# -----------------------------------------------------------------
|
|
# run_repl — multiline
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: run_repl handles multiline input ending with backslash
|
|
Given the REPL input sequence for multiline is "version \" then "" then ":exit"
|
|
And dispatch_command is mocked for run_repl
|
|
When I run the REPL loop
|
|
Then the REPL loop exit code is 0
|
|
|
|
# -----------------------------------------------------------------
|
|
# run_repl — KeyboardInterrupt / EOFError
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: run_repl handles KeyboardInterrupt and continues
|
|
Given the REPL input sequence for interrupt then exit
|
|
When I run the REPL loop
|
|
Then the REPL loop exit code is 0
|
|
|
|
Scenario: run_repl handles EOFError for clean exit
|
|
Given the REPL input sequence is empty
|
|
When I run the REPL loop
|
|
Then the REPL loop exit code is 0
|
|
|
|
# -----------------------------------------------------------------
|
|
# run_repl — shlex parse error
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: run_repl handles shlex parse error gracefully
|
|
Given the REPL input sequence has an unclosed quote then exit
|
|
When I run the REPL loop
|
|
Then the REPL loop output contains "Parse error"
|
|
|
|
# -----------------------------------------------------------------
|
|
# run_repl — history on/off
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: run_repl with history enabled calls setup and save
|
|
Given the REPL input sequence is
|
|
| line |
|
|
| :exit |
|
|
When I run the REPL loop with history enabled
|
|
Then _setup_history was called
|
|
And _save_history was called
|
|
|
|
Scenario: run_repl with no_history skips setup and save
|
|
Given the REPL input sequence is
|
|
| line |
|
|
| :exit |
|
|
When I run the REPL loop with no_history
|
|
Then _setup_history was not called
|
|
And _save_history was not called
|
|
|
|
# -----------------------------------------------------------------
|
|
# run_repl — dispatch a real-ish command
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: run_repl dispatches tokenised command
|
|
Given the REPL input sequence is
|
|
| line |
|
|
| info |
|
|
| :exit |
|
|
And dispatch_command is mocked for run_repl
|
|
When I run the REPL loop
|
|
Then dispatch_command was called with args "info"
|
|
|
|
# -----------------------------------------------------------------
|
|
# repl_callback
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: repl_callback starts REPL when stdout is a tty
|
|
Given sys.stdout.isatty returns True
|
|
And run_repl is mocked to return 0
|
|
When I call repl_callback
|
|
Then run_repl was invoked
|
|
And repl_callback raises SystemExit with code 0
|
|
|
|
Scenario: repl_callback starts REPL when CLEVERAGENTS_FORCE_REPL is set
|
|
Given sys.stdout.isatty returns False
|
|
And CLEVERAGENTS_FORCE_REPL is set in the environment
|
|
And run_repl is mocked to return 0
|
|
When I call repl_callback
|
|
Then run_repl was invoked
|
|
|
|
Scenario: repl_callback does nothing when not a tty and no force env
|
|
Given sys.stdout.isatty returns False
|
|
And CLEVERAGENTS_FORCE_REPL is not set
|
|
When I call repl_callback
|
|
Then run_repl was not invoked
|
|
|
|
# -----------------------------------------------------------------
|
|
# Completer edge case: empty text matches all tokens
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: The installed completer returns first token for empty text
|
|
Given the completer is installed via _setup_completer
|
|
When I query the completer with empty text and state 0
|
|
Then the completer returns a non-None value
|