Feature: DecisionService application-layer coverage
  As a developer
  I want thorough unit tests for every DecisionService method
  So that decision_service.py achieves full line and branch coverage

  # All steps use the "dsvc-" prefix to avoid collisions with other step files.

  # ------------------------------------------------------------------
  # Constructor
  # ------------------------------------------------------------------

  Scenario: dsvc- constructor stores settings and unit_of_work
    Given dsvc- a mock settings object and a mock UnitOfWork
    When dsvc- I construct a DecisionService with those dependencies
    Then dsvc- the service should store the settings
    And dsvc- the service should store the unit_of_work
    And dsvc- the service should have a bound structlog logger

  # ------------------------------------------------------------------
  # record_decision
  # ------------------------------------------------------------------

  Scenario: dsvc- record_decision persists and returns the decision
    Given dsvc- a DecisionService with a mocked UnitOfWork
    When dsvc- I call record_decision with plan_id and required args
    Then dsvc- the UoW transaction should have been entered
    And dsvc- ctx.decisions.create should have been called once
    And dsvc- the returned decision should have the correct plan_id

  Scenario: dsvc- record_decision logs info on success
    Given dsvc- a DecisionService with a mocked UnitOfWork and captured logger
    When dsvc- I call record_decision with plan_id and required args
    Then dsvc- the logger should have recorded an info call with "decision.recorded"

  # ------------------------------------------------------------------
  # get_decision
  # ------------------------------------------------------------------

  Scenario: dsvc- get_decision retrieves a decision by ID
    Given dsvc- a DecisionService with a mocked UnitOfWork
    And dsvc- the mock repo get method returns a Decision
    When dsvc- I call get_decision with a known ID
    Then dsvc- ctx.decisions.get should have been called with the ID
    And dsvc- the returned value should be the expected Decision

  Scenario: dsvc- get_decision raises DecisionNotFoundError when not found
    Given dsvc- a DecisionService with a mocked UnitOfWork
    And dsvc- the mock repo get method returns None
    When dsvc- I call get_decision with an unknown ID expecting not-found
    Then dsvc- a DecisionNotFoundError should have been raised

  # ------------------------------------------------------------------
  # list_decisions
  # ------------------------------------------------------------------

  Scenario: dsvc- list_decisions returns list of decisions
    Given dsvc- a DecisionService with a mocked UnitOfWork
    And dsvc- the mock repo get_by_plan method returns 3 decisions
    When dsvc- I call list_decisions with a plan ID
    Then dsvc- ctx.decisions.get_by_plan should have been called with the plan ID
    And dsvc- the returned list should have 3 decisions

  Scenario: dsvc- list_decisions returns empty list when none exist
    Given dsvc- a DecisionService with a mocked UnitOfWork
    And dsvc- the mock repo get_by_plan method returns 0 decisions
    When dsvc- I call list_decisions with a plan ID
    Then dsvc- the returned list should have 0 decisions

  # ------------------------------------------------------------------
  # get_tree
  # ------------------------------------------------------------------

  Scenario: dsvc- get_tree returns BFS-ordered list
    Given dsvc- a DecisionService with a mocked UnitOfWork
    And dsvc- the mock repo get_by_plan method returns 4 decisions as a tree
    When dsvc- I call get_tree with a plan ID
    Then dsvc- the returned tree list should have 4 decisions

  # ------------------------------------------------------------------
  # get_path_to_root
  # ------------------------------------------------------------------

  Scenario: dsvc- get_path_to_root returns leaf-to-root path
    Given dsvc- a DecisionService with a mocked UnitOfWork
    And dsvc- the mock repo get_path_to_root method returns 3 decisions
    When dsvc- I call get_path_to_root with a leaf ID
    Then dsvc- ctx.decisions.get_path_to_root should have been called with the leaf ID
    And dsvc- the returned path list should have 3 decisions

  # ------------------------------------------------------------------
  # mark_superseded
  # ------------------------------------------------------------------

  Scenario: dsvc- mark_superseded updates and returns the decision
    Given dsvc- a DecisionService with a mocked UnitOfWork
    And dsvc- the mock repo has the replacement decision in cache
    And dsvc- the mock repo update_superseded_by method returns a superseded Decision
    When dsvc- I call mark_superseded with old and new IDs
    Then dsvc- ctx.decisions.update_superseded_by should have been called with both IDs
    And dsvc- the returned decision should be the superseded one

  Scenario: dsvc- mark_superseded logs info with both IDs
    Given dsvc- a DecisionService with a mocked UnitOfWork and captured logger
    And dsvc- the mock repo has the replacement decision in cache
    And dsvc- the mock repo update_superseded_by method returns a superseded Decision
    When dsvc- I call mark_superseded with old and new IDs
    Then dsvc- the logger should have recorded an info call with "decision.superseded"

  # ------------------------------------------------------------------
  # list_by_type
  # ------------------------------------------------------------------

  Scenario: dsvc- list_by_type returns filtered decisions
    Given dsvc- a DecisionService with a mocked UnitOfWork
    And dsvc- the mock repo list_by_type method returns 2 decisions
    When dsvc- I call list_by_type with plan ID and type "strategy_choice"
    Then dsvc- ctx.decisions.list_by_type should have been called with plan ID and type
    And dsvc- the returned type list should have 2 decisions

  Scenario: dsvc- list_by_type returns empty when no matches
    Given dsvc- a DecisionService with a mocked UnitOfWork
    And dsvc- the mock repo list_by_type method returns 0 decisions
    When dsvc- I call list_by_type with plan ID and type "error_recovery"
    Then dsvc- the returned type list should have 0 decisions
