test: add TDD bug-capture test for #989 — JSON decode crash in persistence #1166
@@ -0,0 +1,98 @@
|
||||
"""Steps for TDD bug-capture scenario #989.
|
||||
|
||||
This scenario intentionally fails until the bugfix branch for #989 adds
|
||||
corruption-specific handling around automation profile JSON decoding.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from behave import given, then, when
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from cleveragents.infrastructure.database.models import (
|
||||
AutomationProfileModel,
|
||||
Base,
|
||||
)
|
||||
from cleveragents.infrastructure.database.repositories import (
|
||||
AutomationProfileRepository,
|
||||
)
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.now(UTC).isoformat()
|
||||
|
||||
|
||||
@given("an automation profile repository row with corrupt safety_json for bug 989")
|
||||
def step_given_corrupt_safety_json_row(context: Any) -> None:
|
||||
"""Persist a row that reproduces the malformed JSON decode path in _to_domain."""
|
||||
engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
factory = sessionmaker(bind=engine, expire_on_commit=False, future=True)
|
||||
|
||||
now_iso = _now_iso()
|
||||
with factory() as session:
|
||||
session.add(
|
||||
AutomationProfileModel(
|
||||
name="bug-989-corrupt-json",
|
||||
description="Corrupt JSON regression fixture",
|
||||
|
|
||||
schema_version="1.0",
|
||||
auto_strategize=0.0,
|
||||
auto_execute=0.0,
|
||||
auto_apply=0.0,
|
||||
auto_decisions_strategize=0.0,
|
||||
auto_decisions_execute=0.0,
|
||||
auto_validation_fix=0.0,
|
||||
auto_strategy_revision=0.0,
|
||||
auto_reversion_from_apply=0.0,
|
||||
auto_child_plans=0.0,
|
||||
auto_retry_transient=0.0,
|
||||
auto_checkpoint_restore=0.0,
|
||||
require_sandbox=True,
|
||||
require_checkpoints=True,
|
||||
allow_unsafe_tools=False,
|
||||
# Deliberately malformed JSON (missing closing brace)
|
||||
safety_json='{"require_sandbox": true',
|
||||
guards_json=None,
|
||||
created_at=now_iso,
|
||||
updated_at=now_iso,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
|
||||
context.tdd_989_repo = AutomationProfileRepository(factory)
|
||||
context.tdd_989_error = None
|
||||
|
||||
|
||||
@when("the corrupt automation profile is fetched by name for bug 989")
|
||||
def step_when_fetch_corrupt_profile(context: Any) -> None:
|
||||
"""Execute repository get path that currently leaks JSONDecodeError."""
|
||||
repo: AutomationProfileRepository = context.tdd_989_repo
|
||||
try:
|
||||
repo.get_by_name("bug-989-corrupt-json")
|
||||
except Exception as exc:
|
||||
context.tdd_989_error = exc
|
||||
|
||||
|
||||
@then(
|
||||
"a corruption-specific domain error should be raised instead of JSONDecodeError for bug 989"
|
||||
)
|
||||
def step_then_domain_specific_error(context: Any) -> None:
|
||||
"""Assert expected post-fix behavior (fails today while bug exists)."""
|
||||
err = context.tdd_989_error
|
||||
assert err is not None, (
|
||||
"Expected a corruption-specific domain error, but no error was raised"
|
||||
)
|
||||
assert not isinstance(err, json.JSONDecodeError), (
|
||||
"Expected a domain-specific corruption error, "
|
||||
f"but raw JSONDecodeError leaked: {err}"
|
||||
)
|
||||
name_or_message = f"{type(err).__name__} {err}".lower()
|
||||
assert "corrupt" in name_or_message, (
|
||||
"Expected corruption-specific error semantics (type/message containing 'corrupt'), "
|
||||
f"got {type(err).__name__}: {err}"
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
@tdd_bug @tdd_bug_989 @tdd_expected_fail
|
||||
Feature: TDD Bug #989 — automation profile persistence crashes on corrupt JSON
|
||||
As a developer reading persisted automation profiles
|
||||
I want corrupt JSON payloads to be handled with a domain-specific error
|
||||
So that callers do not receive a raw JSONDecodeError crash
|
||||
|
||||
# This test captures bug #989. It intentionally uses @tdd_expected_fail
|
||||
# until the bugfix for #989 is merged. The underlying assertion currently
|
||||
# fails (proving the bug exists) and the tag inversion makes CI pass.
|
||||
|
||||
Scenario: Bug #989 — get_by_name should not leak JSONDecodeError for corrupt safety_json
|
||||
Given an automation profile repository row with corrupt safety_json for bug 989
|
||||
When the corrupt automation profile is fetched by name for bug 989
|
||||
Then a corruption-specific domain error should be raised instead of JSONDecodeError for bug 989
|
||||
Reference in New Issue
Block a user
Minor: These column names (
auto_strategize,auto_execute,auto_apply, etc.) don't exist onAutomationProfileModel. The actual float threshold columns aredecompose_task,create_tool,select_tool,edit_code,execute_command,create_file,delete_content,access_network,install_dependency,modify_config,approve_plan.This works silently because the model has
__allow_unmapped__ = True, so SQLAlchemy accepts arbitrary kwargs as Python attributes while the actual DB columns get theirdefault=0.0values. The test still correctly captures bug #989 since the focus is onsafety_jsoncorruption, not these threshold values.Non-blocking, but consider updating when the bugfix PR removes
@tdd_expected_fail.