forked from HAL9000/cleveragents-core
5ac1e4eeb3
Write a Behave feature (features/tdd_exec_env_resolution_precedence.feature) with three scenarios that capture bug #1080: the ExecutionEnvironmentResolver does not honour the 6-level execution environment precedence chain defined in the spec (§Execution Environment Routing). The critical scenario ("Project-level override beats plan-level fallback") demonstrates the bug by calling resolve() with plan_env="host" and project_env="container", where the project has priority "override" and the plan has priority "fallback". Per the spec, project override (level 2) should beat plan fallback (level 4), but the current flat resolver chain returns "host" (plan always wins). The @tdd_expected_fail tag inverts this assertion failure to a CI pass. Two regression-guard scenarios (without @tdd_expected_fail) verify: (1) Plan-level override still beats project-level override (level 1 vs 2). (2) Project-level override beats host default (level 2 vs 6). Both pass both competing environments to the resolver to ensure the resolver sees the full context and must choose correctly. Files added: - features/tdd_exec_env_resolution_precedence.feature — tagged with @tdd_bug @tdd_bug_1080 @mock_only and one @tdd_expected_fail scenario - features/steps/tdd_exec_env_resolution_precedence_steps.py — step definitions with priority validation, TODO markers for #1080 cleanup, and module-level imports Robot test: N/A — ExecutionEnvironmentResolver is a pure domain service with no I/O or integration boundaries. ISSUES CLOSED: #1101
175 lines
7.1 KiB
Python
175 lines
7.1 KiB
Python
"""Step definitions for TDD bug #1080 — execution env resolution precedence.
|
|
|
|
This file captures bug #1080: the ExecutionEnvironmentResolver does not
|
|
honour the 6-level precedence chain defined in the spec (§Execution
|
|
Environment Routing). The resolver currently uses a flat chain
|
|
(tool > plan > project > default) without distinguishing "override"
|
|
from "fallback" priority semantics.
|
|
|
|
Scenario 1 is tagged ``@tdd_expected_fail`` so CI passes while the bug
|
|
is unfixed. Scenarios 2 and 3 are regression guards (no expected-fail
|
|
tag) that verify existing correct behaviour is not broken.
|
|
|
|
When the fix for #1080 is implemented:
|
|
|
|
1. Remove the ``@tdd_expected_fail`` tag from Scenario 1 in the feature file.
|
|
2. Update the resolver to implement the 6-level precedence chain with
|
|
priority-aware interleaving.
|
|
3. All three scenarios should then pass normally.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from behave import given, then, when # type: ignore[import-untyped]
|
|
from behave.runner import Context # type: ignore[import-untyped]
|
|
|
|
from cleveragents.application.services.execution_environment_resolver import (
|
|
ExecutionEnvironmentResolver,
|
|
)
|
|
|
|
# Valid priority values accepted by the step definitions. Guards against
|
|
# typos in feature files and ensures parity with the spec's two-mode model.
|
|
_VALID_PRIORITIES: frozenset[str] = frozenset({"override", "fallback"})
|
|
|
|
|
|
@given("a precedence-aware execution environment resolver")
|
|
def step_create_precedence_resolver(context: Context) -> None:
|
|
"""Create an ExecutionEnvironmentResolver for precedence testing."""
|
|
context.precedence_resolver = ExecutionEnvironmentResolver()
|
|
context.precedence_resolved_env = None
|
|
|
|
|
|
@when(
|
|
'I resolve precedence with plan env "{plan_env}" at priority '
|
|
'"{plan_priority}" and project env "{project_env}" at priority '
|
|
'"{project_priority}"'
|
|
)
|
|
def step_resolve_precedence_with_priorities(
|
|
context: Context,
|
|
plan_env: str,
|
|
plan_priority: str,
|
|
project_env: str,
|
|
project_priority: str,
|
|
) -> None:
|
|
"""Resolve execution environment with the 6-level precedence chain.
|
|
|
|
Per the spec, the correct precedence is:
|
|
1. Plan-level with priority: override
|
|
2. Project-level with priority: override
|
|
3. Nearest-ancestor devcontainer
|
|
4. Plan-level with priority: fallback
|
|
5. Project-level with priority: fallback
|
|
6. Host (default)
|
|
|
|
The current ``resolver.resolve()`` uses a flat chain that ignores
|
|
priority, so this test will produce incorrect results for Scenario 1
|
|
until the bug is fixed. Scenarios 2 and 3 exercise paths that
|
|
happen to work correctly with the flat chain but still route through
|
|
this step to ensure the resolver sees both competing environments.
|
|
"""
|
|
# Validate priority values against the spec's two-mode model.
|
|
assert plan_priority in _VALID_PRIORITIES, (
|
|
f"Invalid plan_priority '{plan_priority}'; "
|
|
f"expected one of {sorted(_VALID_PRIORITIES)}"
|
|
)
|
|
assert project_priority in _VALID_PRIORITIES, (
|
|
f"Invalid project_priority '{project_priority}'; "
|
|
f"expected one of {sorted(_VALID_PRIORITIES)}"
|
|
)
|
|
|
|
resolver: ExecutionEnvironmentResolver = context.precedence_resolver
|
|
|
|
# TODO(#1080): The 6-level precedence chain is built manually here
|
|
# because the current resolver.resolve() uses a flat priority chain
|
|
# that ignores priority semantics. When the bug is fixed, this
|
|
# manual chain should be replaced by a single call to the resolver's
|
|
# new priority-aware API.
|
|
|
|
# Level 1: plan override
|
|
if plan_env and plan_priority == "override":
|
|
# Pass both plan_env AND project_env so the resolver must choose
|
|
# correctly when competing environments are present.
|
|
context.precedence_resolved_env = resolver.resolve(
|
|
plan_env=plan_env,
|
|
project_env=project_env,
|
|
)
|
|
return
|
|
|
|
# Level 2: project override
|
|
if project_env and project_priority == "override":
|
|
# BUG: the current resolver.resolve() does not support this
|
|
# precedence level. Calling resolve(plan_env=..., project_env=...)
|
|
# always returns plan_env first. We pass both plan_env and
|
|
# project_env to the resolver and assert the result matches the
|
|
# project override. The current flat chain will return plan_env
|
|
# instead — triggering the expected assertion failure.
|
|
context.precedence_resolved_env = resolver.resolve(
|
|
plan_env=plan_env,
|
|
project_env=project_env,
|
|
)
|
|
return
|
|
|
|
# Level 3: devcontainer (not tested in this scenario)
|
|
|
|
# Level 4: plan fallback
|
|
if plan_env and plan_priority == "fallback":
|
|
context.precedence_resolved_env = resolver.resolve(plan_env=plan_env)
|
|
return
|
|
|
|
# Level 5: project fallback
|
|
if project_env and project_priority == "fallback":
|
|
context.precedence_resolved_env = resolver.resolve(project_env=project_env)
|
|
return
|
|
|
|
# Level 6: host default
|
|
context.precedence_resolved_env = resolver.resolve()
|
|
|
|
|
|
@when(
|
|
'I resolve precedence with no plan env and project env "{project_env}" '
|
|
'at priority "{project_priority}"'
|
|
)
|
|
def step_resolve_precedence_project_only(
|
|
context: Context,
|
|
project_env: str,
|
|
project_priority: str,
|
|
) -> None:
|
|
"""Resolve with project-level environment and no plan-level environment.
|
|
|
|
When no plan-level environment is set, the project-level environment
|
|
should be returned regardless of priority mode. This scenario acts
|
|
as a regression guard: the current resolver handles this case
|
|
correctly (project_env is returned when plan_env is None), but the
|
|
resolution path does not check priority — it works only by accident.
|
|
|
|
This scenario ensures the fix correctly handles the project-override
|
|
path and doesn't regress the simple case.
|
|
"""
|
|
# Validate the priority parameter to catch silent discards.
|
|
assert project_priority in _VALID_PRIORITIES, (
|
|
f"Invalid project_priority '{project_priority}'; "
|
|
f"expected one of {sorted(_VALID_PRIORITIES)}"
|
|
)
|
|
|
|
resolver: ExecutionEnvironmentResolver = context.precedence_resolver
|
|
context.precedence_resolved_env = resolver.resolve(project_env=project_env)
|
|
|
|
|
|
@then('the precedence-resolved environment should be "{expected}"')
|
|
def step_check_precedence_result(context: Context, expected: str) -> None:
|
|
"""Assert the resolved environment matches the expected value.
|
|
|
|
For bug #1080, the critical assertion is in the scenario
|
|
'Project-level override beats plan-level fallback': the resolver
|
|
is called with plan_env="host" and project_env="container", but
|
|
the expected result is "container" (project override at level 2
|
|
beats plan fallback at level 4). The current resolver returns
|
|
"host" because it always picks plan_env before project_env.
|
|
"""
|
|
assert context.precedence_resolved_env is not None, "No environment was resolved"
|
|
assert context.precedence_resolved_env.value == expected, (
|
|
f"Expected execution environment '{expected}' but got "
|
|
f"'{context.precedence_resolved_env.value}'. "
|
|
f"Bug #1080: resolver does not honour 6-level precedence chain."
|
|
)
|