eb46f0ff54
CI / push-validation (push) Successful in 40s
CI / helm (push) Successful in 44s
CI / build (push) Successful in 1m14s
CI / lint (push) Successful in 1m18s
CI / quality (push) Successful in 1m36s
CI / e2e_tests (push) Successful in 1m19s
CI / typecheck (push) Successful in 2m22s
CI / security (push) Successful in 2m23s
CI / benchmark-regression (push) Failing after 41s
CI / integration_tests (push) Successful in 4m46s
CI / unit_tests (push) Failing after 20m13s
CI / benchmark-publish (push) Failing after 28m28s
CI / coverage (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / status-check (push) Has been cancelled
## Summary This PR fixes issue #10878 where architecture reviews were truncated because the regex pattern for parsing file output would stop at the first ``` encountered in the Markdown report. ## Changes - Change file delimiters from ``` to >>>>>>>/<<<<<<< to avoid Markdown conflicts - Add tier hydration before strategize phase in plan_executor.py - Increase max_tokens to 16384 in llm_actors.py for longer outputs - Increase context_max_tokens_hot from 16000 to 32000 in settings.py - Fix get_hot_view → get_hot_fragments in strategy_actor.py and plan_executor.py - Add opencode to skip directories in context_tier_hydrator.py - Change sandbox output location to plan-output/ directory in plan.py - Add get_context_summary stub method to acms_service.py ## Testing Run architecture review action and verify the output report is complete with all sections. Reviewed-on: #10938
312 lines
9.5 KiB
Python
312 lines
9.5 KiB
Python
"""Step definitions for validation-gated apply pipeline tests.
|
|
|
|
Uses mock objects for PlanLifecycleService and Plan to test the
|
|
validation gate logic in PlanApplyService without needing a database.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.application.services.plan_apply_service import (
|
|
ApplyOutcome,
|
|
PlanApplyService,
|
|
)
|
|
from cleveragents.domain.models.core.change import (
|
|
ChangeEntry,
|
|
ChangeOperation,
|
|
SpecChangeSet,
|
|
)
|
|
from cleveragents.domain.models.core.plan import (
|
|
PlanPhase,
|
|
PlanTimestamps,
|
|
ProcessingState,
|
|
)
|
|
|
|
__all__: list[str] = []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Mock helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_PLAN_ID = "01PLANTEST000000000000001"
|
|
|
|
|
|
def _make_mock_plan(
|
|
*,
|
|
plan_id: str = _PLAN_ID,
|
|
phase: PlanPhase = PlanPhase.EXECUTE,
|
|
state: ProcessingState = ProcessingState.COMPLETE,
|
|
changeset_id: str | None = "CS001",
|
|
validation_summary: dict[str, Any] | None = None,
|
|
is_terminal: bool = False,
|
|
) -> MagicMock:
|
|
"""Create a mock Plan object."""
|
|
plan = MagicMock()
|
|
plan.identity.plan_id = plan_id
|
|
plan.phase = phase
|
|
plan.processing_state = state
|
|
plan.changeset_id = changeset_id
|
|
plan.validation_summary = validation_summary
|
|
plan.is_terminal = is_terminal
|
|
plan.error_details = None
|
|
plan.timestamps = PlanTimestamps()
|
|
return plan
|
|
|
|
|
|
def _make_changeset(
|
|
plan_id: str = _PLAN_ID,
|
|
n_files: int = 1,
|
|
) -> SpecChangeSet:
|
|
"""Create a SpecChangeSet with n_files entries."""
|
|
cs = SpecChangeSet(plan_id=plan_id)
|
|
for i in range(n_files):
|
|
cs.add_change(
|
|
ChangeEntry(
|
|
plan_id=plan_id,
|
|
resource_id="RES001",
|
|
tool_name="builtin/test-tool",
|
|
operation=ChangeOperation.MODIFY,
|
|
path=f"src/file_{i}.py",
|
|
before_hash=f"before-{i}",
|
|
after_hash=f"after-{i}",
|
|
)
|
|
)
|
|
return cs
|
|
|
|
|
|
def _make_service(
|
|
plan: MagicMock,
|
|
changeset: SpecChangeSet | None = None,
|
|
) -> PlanApplyService:
|
|
"""Create a PlanApplyService with mocked lifecycle and store."""
|
|
lifecycle = MagicMock()
|
|
lifecycle.get_plan.return_value = plan
|
|
lifecycle.complete_apply.return_value = plan
|
|
lifecycle.constrain_apply.return_value = plan
|
|
lifecycle.commit_plan = MagicMock()
|
|
|
|
store = MagicMock()
|
|
if changeset is not None:
|
|
store.get.return_value = changeset
|
|
else:
|
|
store.get.return_value = None
|
|
|
|
return PlanApplyService(
|
|
lifecycle_service=lifecycle,
|
|
changeset_store=store,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Background
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("an apply pipeline test environment")
|
|
def step_given_apply_env(context: Context) -> None:
|
|
context.ap_plan_id = _PLAN_ID
|
|
context.ap_plan = None
|
|
context.ap_changeset = None
|
|
context.ap_service = None
|
|
context.ap_result = None # ApplyResult | None
|
|
context.ap_allow_empty = False
|
|
context.ap_external_summary = None # dict[str, Any] | None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a plan with changeset containing {n} files")
|
|
def step_given_plan_with_files(context: Context, n: str) -> None:
|
|
cs = _make_changeset(n_files=int(n))
|
|
plan = _make_mock_plan(changeset_id=cs.changeset_id)
|
|
context.ap_plan = plan
|
|
context.ap_changeset = cs
|
|
context.ap_service = _make_service(plan, cs)
|
|
|
|
|
|
@given("the plan has validation summary with {p} required passed and {f} failed")
|
|
def step_given_validation_summary(context: Context, p: str, f: str) -> None:
|
|
vs = {
|
|
"total": int(p) + int(f),
|
|
"required_passed": int(p),
|
|
"required_failed": int(f),
|
|
"informational_passed": 0,
|
|
"informational_failed": 0,
|
|
}
|
|
context.ap_plan.validation_summary = vs
|
|
|
|
|
|
@given(
|
|
"the plan has validation summary with {p} required passed "
|
|
"and {f} failed and {i} informational"
|
|
)
|
|
def step_given_validation_summary_with_info(
|
|
context: Context, p: str, f: str, i: str
|
|
) -> None:
|
|
vs = {
|
|
"total": int(p) + int(f) + int(i),
|
|
"required_passed": int(p),
|
|
"required_failed": int(f),
|
|
"informational_passed": int(i),
|
|
"informational_failed": 0,
|
|
}
|
|
context.ap_plan.validation_summary = vs
|
|
|
|
|
|
@given("the plan has no validation summary")
|
|
def step_given_no_validation_summary(context: Context) -> None:
|
|
context.ap_plan.validation_summary = None
|
|
|
|
|
|
@given("a plan that is already applied")
|
|
def step_given_already_applied(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.APPLY,
|
|
state=ProcessingState.APPLIED,
|
|
is_terminal=True,
|
|
)
|
|
cs = _make_changeset()
|
|
context.ap_plan = plan
|
|
context.ap_changeset = cs
|
|
context.ap_service = _make_service(plan, cs)
|
|
|
|
|
|
@given("a plan that is in errored state")
|
|
def step_given_errored_plan(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.APPLY,
|
|
state=ProcessingState.ERRORED,
|
|
is_terminal=True,
|
|
)
|
|
cs = _make_changeset()
|
|
context.ap_plan = plan
|
|
context.ap_changeset = cs
|
|
context.ap_service = _make_service(plan, cs)
|
|
|
|
|
|
@given("a plan that is in cancelled state")
|
|
def step_given_cancelled_plan(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.APPLY,
|
|
state=ProcessingState.CANCELLED,
|
|
is_terminal=True,
|
|
)
|
|
cs = _make_changeset()
|
|
context.ap_plan = plan
|
|
context.ap_changeset = cs
|
|
context.ap_service = _make_service(plan, cs)
|
|
|
|
|
|
@given("a plan with empty changeset")
|
|
def step_given_empty_changeset(context: Context) -> None:
|
|
cs = _make_changeset(n_files=0)
|
|
plan = _make_mock_plan(changeset_id=cs.changeset_id)
|
|
context.ap_plan = plan
|
|
context.ap_changeset = cs
|
|
context.ap_service = _make_service(plan, cs)
|
|
|
|
|
|
@given("an apply plan with no changeset assigned")
|
|
def step_given_no_changeset(context: Context) -> None:
|
|
plan = _make_mock_plan(changeset_id=None)
|
|
context.ap_plan = plan
|
|
context.ap_changeset = None
|
|
context.ap_service = _make_service(plan, None)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I apply the plan with validation gate")
|
|
def step_when_apply(context: Context) -> None:
|
|
context.ap_result = context.ap_service.apply_with_validation_gate(
|
|
plan_id=context.ap_plan_id,
|
|
allow_empty=context.ap_allow_empty,
|
|
)
|
|
|
|
|
|
@when("I apply the plan with validation gate and allow_empty")
|
|
def step_when_apply_allow_empty(context: Context) -> None:
|
|
context.ap_result = context.ap_service.apply_with_validation_gate(
|
|
plan_id=context.ap_plan_id,
|
|
allow_empty=True,
|
|
)
|
|
|
|
|
|
@when(
|
|
"I apply the plan with external validation summary having {p} passed and {f} failed"
|
|
)
|
|
def step_when_apply_external_summary(context: Context, p: str, f: str) -> None:
|
|
ext_summary = {
|
|
"total": int(p) + int(f),
|
|
"required_passed": int(p),
|
|
"required_failed": int(f),
|
|
}
|
|
context.ap_result = context.ap_service.apply_with_validation_gate(
|
|
plan_id=context.ap_plan_id,
|
|
validation_summary=ext_summary,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the apply outcome should be "{outcome}"')
|
|
def step_then_outcome(context: Context, outcome: str) -> None:
|
|
assert context.ap_result is not None, "No apply result"
|
|
expected = ApplyOutcome(outcome)
|
|
assert context.ap_result.outcome == expected, (
|
|
f"Expected outcome '{outcome}', got '{context.ap_result.outcome}'"
|
|
)
|
|
|
|
|
|
@then("the apply result files changed should be {n}")
|
|
def step_then_files_changed(context: Context, n: str) -> None:
|
|
assert context.ap_result is not None
|
|
assert context.ap_result.files_changed == int(n), (
|
|
f"Expected files_changed={n}, got {context.ap_result.files_changed}"
|
|
)
|
|
|
|
|
|
@then("the apply result validations total should be {n}")
|
|
def step_then_validations_total(context: Context, n: str) -> None:
|
|
assert context.ap_result is not None
|
|
assert context.ap_result.validations_total == int(n), (
|
|
f"Expected validations_total={n}, got {context.ap_result.validations_total}"
|
|
)
|
|
|
|
|
|
@then("the apply result validations failed should be {n}")
|
|
def step_then_validations_failed(context: Context, n: str) -> None:
|
|
assert context.ap_result is not None
|
|
assert context.ap_result.validations_failed == int(n), (
|
|
f"Expected validations_failed={n}, got {context.ap_result.validations_failed}"
|
|
)
|
|
|
|
|
|
@then('the apply result message should contain "{text}"')
|
|
def step_then_message_contains(context: Context, text: str) -> None:
|
|
assert context.ap_result is not None
|
|
assert text in context.ap_result.message, (
|
|
f"Expected message to contain '{text}', got: {context.ap_result.message}"
|
|
)
|
|
|
|
|
|
@then("the apply result plan_id should match the plan ID")
|
|
def step_then_plan_id_matches(context: Context) -> None:
|
|
assert context.ap_result is not None
|
|
assert context.ap_result.plan_id == context.ap_plan_id
|