Files
cleveragents-core/features/repositories_coverage_r2.feature
freemo a808c395f9
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 17s
CI / typecheck (pull_request) Successful in 35s
CI / security (pull_request) Successful in 50s
CI / unit_tests (pull_request) Successful in 2m46s
CI / integration_tests (pull_request) Successful in 3m16s
CI / docker (pull_request) Successful in 40s
CI / coverage (pull_request) Successful in 5m6s
CI / lint (push) Successful in 13s
CI / quality (push) Successful in 16s
CI / build (push) Successful in 18s
CI / security (push) Successful in 32s
CI / typecheck (push) Successful in 35s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m52s
CI / integration_tests (push) Successful in 3m8s
CI / docker (push) Successful in 39s
CI / coverage (push) Successful in 5m53s
CI / benchmark-publish (push) Successful in 16m55s
CI / benchmark-regression (pull_request) Successful in 33m0s
test(coverage): add Behave BDD tests to improve unit test coverage across 53 source modules
Add 53 new .feature files and corresponding step definition files targeting
uncovered lines identified in build/coverage.xml. Fix AmbiguousStep conflicts
in 7 pre-existing step files by disambiguating step text.

New tests cover: ACP clients/facade, actor CLI/config, application container,
ACMS service/strategies, async worker, automation profile CLI, autonomy
guardrail, bridge, change model, config CLI/service, context service,
cross-plan correction, database models, decision service, decomposition
clustering/service, discovery handler, langchain chat provider, langgraph
nodes, materializers, multi-project service, plan apply/CLI/lifecycle/model/
preflight/resume/service, PostgreSQL analyzer, project CLI/context CLI,
provider registry, reactive application/route, repositories, resolver handler,
resource registry service, resume model, retry patterns, sandbox protocol,
server CLI, skill CLI/service, skills registry, subplan execution/service,
system CLI, UKO loader, UoW, and YAML template engine.

Closes #645
2026-03-09 13:01:58 -04:00

301 lines
14 KiB
Gherkin
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@unit @repository @coverage
Feature: Repository coverage round 2 - Skill, Decision, Checkpoint, and ToolRepository
Exercise uncovered lines in repositories.py for SkillRepository flattened-tool
cache methods, DecisionRepository full CRUD, CheckpointRepository full CRUD,
and ToolRepository legacy wrapper error paths.
# ===========================================================================
# SkillRepository flattened tool cache methods (lines 4656-4821)
# ===========================================================================
Background:
Given r2cov a fresh in-memory database with all tables
# --- update_flattened_tools ------------------------------------------------
@skill @cache
Scenario: SkillRepository.update_flattened_tools persists cache data for an existing skill
Given r2cov a skill "local/my-skill" exists in the database
When r2cov update_flattened_tools is called for "local/my-skill" with valid cache data
Then r2cov the skill row should have non-null flattened_tools_json
And r2cov the skill row should have the expected flattening_hash
@skill @cache @error
Scenario: SkillRepository.update_flattened_tools raises SkillNotFoundError for missing skill
When r2cov update_flattened_tools is called for "local/nonexistent" with valid cache data
Then r2cov a SkillNotFoundError should be raised
# --- get_flattened_tools ---------------------------------------------------
@skill @cache
Scenario: SkillRepository.get_flattened_tools returns cache dict for existing skill
Given r2cov a skill "local/cached-skill" exists in the database
And r2cov the skill "local/cached-skill" has flattened tools cached
When r2cov get_flattened_tools is called for "local/cached-skill"
Then r2cov the result should be a dict with flattened_tools_json key
And r2cov the result flattening_hash should not be None
@skill @cache @error
Scenario: SkillRepository.get_flattened_tools raises SkillNotFoundError for missing skill
When r2cov get_flattened_tools is called for "local/ghost-skill"
Then r2cov a SkillNotFoundError should be raised
# --- needs_refresh ---------------------------------------------------------
@skill @cache
Scenario: SkillRepository.needs_refresh returns True when hash is missing
Given r2cov a skill "local/stale-skill" exists in the database
When r2cov needs_refresh is called for "local/stale-skill" with hash "abc123"
Then r2cov the result should be True
@skill @cache
Scenario: SkillRepository.needs_refresh returns False when hash matches
Given r2cov a skill "local/fresh-skill" exists in the database
And r2cov the skill "local/fresh-skill" has flattening_hash "abc123"
When r2cov needs_refresh is called for "local/fresh-skill" with hash "abc123"
Then r2cov the result should be False
@skill @cache
Scenario: SkillRepository.needs_refresh returns True when hash differs
Given r2cov a skill "local/changed-skill" exists in the database
And r2cov the skill "local/changed-skill" has flattening_hash "old_hash"
When r2cov needs_refresh is called for "local/changed-skill" with hash "new_hash"
Then r2cov the result should be True
@skill @cache @error
Scenario: SkillRepository.needs_refresh raises SkillNotFoundError for missing skill
When r2cov needs_refresh is called for "local/missing" with hash "abc"
Then r2cov a SkillNotFoundError should be raised
# --- recompute_flattening_hash ---------------------------------------------
@skill @cache
Scenario: SkillRepository.recompute_flattening_hash computes and stores SHA-256
Given r2cov a skill "local/hash-skill" exists in the database
When r2cov recompute_flattening_hash is called for "local/hash-skill" with yaml "name: test"
Then r2cov the returned hash should be the SHA-256 of "name: test"
And r2cov the skill row "local/hash-skill" should have the recomputed hash stored
@skill @cache @error
Scenario: SkillRepository.recompute_flattening_hash raises SkillNotFoundError for missing skill
When r2cov recompute_flattening_hash is called for "local/nope" with yaml "x"
Then r2cov a SkillNotFoundError should be raised
# --- invalidate_cached_summaries -------------------------------------------
@skill @cache
Scenario: SkillRepository.invalidate_cached_summaries nulls cached fields
Given r2cov a skill "local/inv-skill" exists in the database
And r2cov the skill "local/inv-skill" has flattened tools cached
When r2cov invalidate_cached_summaries is called for "local/inv-skill"
Then r2cov the skill row "local/inv-skill" should have null flattened_tools_json
And r2cov the skill row "local/inv-skill" should have null flattening_hash
@skill @cache @error
Scenario: SkillRepository.invalidate_cached_summaries raises SkillNotFoundError for missing skill
When r2cov invalidate_cached_summaries is called for "local/void"
Then r2cov a SkillNotFoundError should be raised
# ===========================================================================
# DecisionRepository CRUD (lines 4841-5255)
# ===========================================================================
# --- create ----------------------------------------------------------------
@decision
Scenario: DecisionRepository.create persists a new decision
Given r2cov a lifecycle plan exists for decisions
When r2cov a decision is created for that plan
Then r2cov the decision should be retrievable by ID
@decision @error
Scenario: DecisionRepository.create raises DuplicateDecisionError on duplicate
Given r2cov a lifecycle plan exists for decisions
And r2cov a decision "01HGZ6FE0AQDYTR4BX000DEC01" exists for that plan
When r2cov creating a decision with the same ID is attempted
Then r2cov a DuplicateDecisionError should be raised
# --- get -------------------------------------------------------------------
@decision
Scenario: DecisionRepository.get returns None for missing decision
When r2cov getting decision "01HGZ6FE0AQDYTR4BX000MISS0" is attempted
Then r2cov the result should be None
# --- get_by_plan -----------------------------------------------------------
@decision
Scenario: DecisionRepository.get_by_plan returns decisions ordered by sequence
Given r2cov a lifecycle plan exists for decisions
And r2cov decisions with sequence 0, 1, 2 exist for that plan
When r2cov get_by_plan is called for that plan
Then r2cov 3 decisions should be returned in sequence order
# --- get_tree --------------------------------------------------------------
@decision
Scenario: DecisionRepository.get_tree returns BFS-ordered tree
Given r2cov a lifecycle plan exists for decisions
And r2cov a decision tree with root and two children exists
When r2cov get_tree is called with the root decision ID
Then r2cov 3 decisions should be returned with root first
@decision @error
Scenario: DecisionRepository.get_tree raises DecisionNotFoundError for missing root
When r2cov get_tree is called with a nonexistent root ID
Then r2cov a DecisionNotFoundError should be raised
# --- get_path_to_root ------------------------------------------------------
@decision
Scenario: DecisionRepository.get_path_to_root walks from leaf to root
Given r2cov a lifecycle plan exists for decisions
And r2cov a decision chain root -> mid -> leaf exists
When r2cov get_path_to_root is called with the leaf decision ID
Then r2cov the path should contain 3 decisions starting with leaf ending with root
@decision @error
Scenario: DecisionRepository.get_path_to_root raises DecisionNotFoundError for missing start
When r2cov get_path_to_root is called with a nonexistent ID
Then r2cov a DecisionNotFoundError should be raised
# --- get_superseded --------------------------------------------------------
@decision
Scenario: DecisionRepository.get_superseded returns superseded decisions
Given r2cov a lifecycle plan exists for decisions
And r2cov a decision that has been superseded exists
When r2cov get_superseded is called for that plan
Then r2cov exactly 1 superseded decision should be returned
# --- update_superseded_by --------------------------------------------------
@decision
Scenario: DecisionRepository.update_superseded_by marks a decision as superseded
Given r2cov a lifecycle plan exists for decisions
And r2cov an original decision and a replacement decision exist
When r2cov update_superseded_by is called on the original with the replacement ID
Then r2cov the original decision should have superseded_by set
@decision @error
Scenario: DecisionRepository.update_superseded_by raises DecisionNotFoundError for missing
When r2cov update_superseded_by is called with a nonexistent decision ID
Then r2cov a DecisionNotFoundError should be raised
# --- list_by_type ----------------------------------------------------------
@decision
Scenario: DecisionRepository.list_by_type filters decisions by type
Given r2cov a lifecycle plan exists for decisions
And r2cov decisions of type "strategy_choice" and "implementation_choice" exist
When r2cov list_by_type is called with "strategy_choice"
Then r2cov only strategy_choice decisions should be returned
# --- delete ----------------------------------------------------------------
@decision
Scenario: DecisionRepository.delete removes a decision
Given r2cov a lifecycle plan exists for decisions
And r2cov a single decision exists for deletion
When r2cov delete is called on the decision
Then r2cov delete should return True
And r2cov the decision should no longer be retrievable
@decision
Scenario: DecisionRepository.delete returns False for missing decision
When r2cov delete is called with a nonexistent decision ID
Then r2cov delete should return False
# --- get_max_sequence_number -----------------------------------------------
@decision
Scenario: DecisionRepository.get_max_sequence_number returns highest sequence
Given r2cov a lifecycle plan exists for decisions
And r2cov decisions with sequence 0, 1, 2 exist for that plan
When r2cov get_max_sequence_number is called for that plan
Then r2cov the result should be 2
# --- count -----------------------------------------------------------------
@decision
Scenario: DecisionRepository.count returns number of decisions
Given r2cov a lifecycle plan exists for decisions
And r2cov decisions with sequence 0, 1, 2 exist for that plan
When r2cov count is called for that plan
Then r2cov the count result should be 3
# ===========================================================================
# CheckpointRepository CRUD (lines 5279-5453)
# ===========================================================================
# --- create ----------------------------------------------------------------
@checkpoint
Scenario: CheckpointRepository.create persists a new checkpoint
Given r2cov a lifecycle plan exists for checkpoints
When r2cov a checkpoint is created for that plan
Then r2cov the checkpoint should be retrievable by ID
# --- get_by_id -------------------------------------------------------------
@checkpoint @error
Scenario: CheckpointRepository.get_by_id raises CheckpointNotFoundError for missing
When r2cov getting checkpoint "01HGZ6FE0AQDYTR4BXCHK00000" is attempted
Then r2cov a CheckpointNotFoundError should be raised
# --- list_by_plan ----------------------------------------------------------
@checkpoint
Scenario: CheckpointRepository.list_by_plan returns checkpoints ordered by creation time
Given r2cov a lifecycle plan exists for checkpoints
And r2cov 3 checkpoints exist for that plan
When r2cov list_by_plan is called for that plan
Then r2cov 3 checkpoints should be returned in creation order
# --- delete ----------------------------------------------------------------
@checkpoint
Scenario: CheckpointRepository.delete removes a checkpoint
Given r2cov a lifecycle plan exists for checkpoints
And r2cov a single checkpoint exists for deletion
When r2cov delete_checkpoint is called on the checkpoint
Then r2cov checkpoint delete should return True
@checkpoint
Scenario: CheckpointRepository.delete returns False for missing checkpoint
When r2cov delete_checkpoint is called with a nonexistent ID
Then r2cov checkpoint delete should return False
# --- prune -----------------------------------------------------------------
@checkpoint
Scenario: CheckpointRepository.prune removes excess interior checkpoints
Given r2cov a lifecycle plan exists for checkpoints
And r2cov 5 checkpoints exist for that plan
When r2cov prune is called with max_checkpoints 3
Then r2cov 2 checkpoint IDs should be returned as pruned
And r2cov only 3 checkpoints should remain for the plan
@checkpoint
Scenario: CheckpointRepository.prune does nothing when under limit
Given r2cov a lifecycle plan exists for checkpoints
And r2cov 2 checkpoints exist for that plan
When r2cov prune is called with max_checkpoints 5
Then r2cov 0 checkpoint IDs should be returned as pruned
# ===========================================================================
# ToolRepository legacy wrapper error paths (lines 3568-3595)
# ===========================================================================
@tool @error
Scenario: ToolRepository.get_by_name raises DatabaseError on OperationalError
Given r2cov a tool repository with a broken session
When r2cov ToolRepository.get_by_name is called with "local/broken"
Then r2cov a DatabaseError should be raised containing "Failed to get tool"
@tool @error
Scenario: ToolRepository.remove raises ToolNotFoundError when tool does not exist
Given r2cov a tool repository backed by an empty database
When r2cov ToolRepository.remove is called with "local/nonexistent"
Then r2cov a ToolNotFoundError should be raised