Files
temp/features/steps/sql_string_aware_coverage_steps.py
freemo 051ee7c290 test(coverage): add Behave BDD tests to improve coverage across 52 source files
Added 52 new .feature files and corresponding _steps.py files targeting
previously uncovered code paths in the following areas:

- TUI layer: app, commands, persona (state/schema/registry), widgets,
  input (shell_exec, reference_parser)
- Application services: plan lifecycle/service/executor, session,
  project, repo indexing, correction, checkpoint, actor, llm_actors,
  strategy coordinator, resource file watcher, service retry wiring
- CLI commands: session, resource, repl, plan, db, automation_profile
- Domain models: retry_policy, resource_type, cost_budget,
  docker_compose_analyzer, detail_level, _sql_string_aware,
  _postgresql_helpers
- Core: circuit_breaker, retry_service_patterns
- Infrastructure: repositories, transaction_sandbox, strategy_registry,
  plugins/loader, container
- Config: settings
- Agents: plan_generation, context_analysis, auto_debug
- A2A: facade

All new tests follow the Behave/Gherkin BDD standard. Resolved step
definition collisions with unique prefixes. Fixed Alembic fileConfig
logger disabling issue (disable_existing_loggers=False).

ISSUES CLOSED: #1068
2026-03-20 21:22:10 +00:00

216 lines
9.0 KiB
Python

"""Step definitions for sql_string_aware_coverage.feature.
These steps target specific uncovered lines in _sql_string_aware.py:
- Line 40: escaped single-quote handling (j += 2) in strip_sql_comments
- Lines 52-58: terminated dollar-quoted strings in strip_sql_comments
- Lines 60-62: unterminated dollar-quoted strings in strip_sql_comments
- Lines 70-71: nested block comment depth increment in strip_sql_comments
- Line 115: escaped single-quote handling (i += 2) in find_unquoted_semicolon
- Lines 125-130: dollar-quoted string handling in find_unquoted_semicolon
"""
from behave import given, then, when
from cleveragents.domain.models.acms._sql_string_aware import (
find_unquoted_semicolon,
strip_sql_comments,
)
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the sql_string_aware module is imported")
def step_module_imported(context):
"""Ensure the module is importable."""
assert strip_sql_comments is not None
assert find_unquoted_semicolon is not None
# ---------------------------------------------------------------------------
# Generic helpers for setting SQL content and calling functions
# ---------------------------------------------------------------------------
@given('SQL content "{sql}"')
def step_set_sql_content(context, sql):
"""Store literal SQL content on the context."""
context.sql_content = sql
@when("I strip SQL comments")
def step_strip_comments(context):
"""Call strip_sql_comments on the stored content."""
context.stripped_result = strip_sql_comments(context.sql_content)
@then('the stripped result should be "{expected}"')
def step_verify_stripped(context, expected):
"""Assert the stripped result matches the expected string."""
assert context.stripped_result == expected, (
f"Expected {expected!r}, got {context.stripped_result!r}"
)
@when("I search for the first unquoted semicolon")
def step_find_semicolon(context):
"""Call find_unquoted_semicolon on the stored content."""
context.semicolon_index = find_unquoted_semicolon(context.sql_content)
# ---------------------------------------------------------------------------
# strip_sql_comments — terminated anonymous dollar-quoted string (lines 52-58)
# ---------------------------------------------------------------------------
@given("SQL content with a terminated anonymous dollar-quoted string")
def step_sql_anon_dollar_string(context):
"""Create SQL with $$body$$ — anonymous dollar-quoted string."""
context.sql_content = "SELECT $$hello world$$"
@then("the dollar-quoted body should be preserved intact")
def step_verify_anon_dollar_preserved(context):
"""Verify the anonymous dollar-quoted string is preserved."""
assert context.stripped_result == "SELECT $$hello world$$", (
f"Got {context.stripped_result!r}"
)
# ---------------------------------------------------------------------------
# strip_sql_comments — terminated named dollar-quoted string (lines 52-58)
# ---------------------------------------------------------------------------
@given("SQL content with a terminated named dollar-quoted string")
def step_sql_named_dollar_string(context):
"""Create SQL with $tag$body$tag$ — named dollar-quoted string."""
context.sql_content = "SELECT $fn$CREATE FUNCTION$fn$ AS val"
@then("the named dollar-quoted body should be preserved intact")
def step_verify_named_dollar_preserved(context):
"""Verify the named dollar-quoted string is preserved."""
assert context.stripped_result == "SELECT $fn$CREATE FUNCTION$fn$ AS val", (
f"Got {context.stripped_result!r}"
)
# ---------------------------------------------------------------------------
# strip_sql_comments — unterminated dollar-quoted string (lines 60-62)
# ---------------------------------------------------------------------------
@given("SQL content with an unterminated dollar-quoted string")
def step_sql_unterminated_dollar_string(context):
"""Create SQL with a $$ that never closes."""
context.sql_content = "SELECT $$this never ends"
@then("the rest of the content should be preserved as-is")
def step_verify_unterminated_dollar_preserved(context):
"""The entire remainder from $$ onwards should be kept."""
assert context.stripped_result == "SELECT $$this never ends", (
f"Got {context.stripped_result!r}"
)
# ---------------------------------------------------------------------------
# find_unquoted_semicolon — escaped single quotes (line 115)
# ---------------------------------------------------------------------------
@then("the semicolon index should point to the one after the closing quote")
def step_verify_semicolon_after_quote(context):
"""The semicolon at the position right after the closing quote.
Content: SELECT 'it''s;here'; DROP TABLE
Indices: 0123456789...
The string 'it''s;here' starts at index 7 and ends at index 19 (the closing ')
The unquoted ; is at index 20.
"""
sql = context.sql_content
# The unquoted semicolon should be at the position of ';' after the closing quote
idx = context.semicolon_index
assert idx == sql.index(";", sql.rindex("'") + 1), (
f"Expected semicolon after closing quote, got index {idx}"
)
@then("the semicolon index should be {index:d}")
def step_verify_semicolon_index(context, index):
"""Assert the semicolon index matches the expected value."""
assert context.semicolon_index == index, (
f"Expected {index}, got {context.semicolon_index}"
)
# ---------------------------------------------------------------------------
# find_unquoted_semicolon — terminated dollar-quoted strings (lines 125-129)
# ---------------------------------------------------------------------------
@given(
"SQL content with dollar-quoted string containing a semicolon then real semicolon"
)
def step_sql_dollar_with_inner_semi(context):
"""Content: $$a;b$$;X — semicolon inside $$ should be skipped."""
context.sql_content = "$$a;b$$;X"
@then("the semicolon should be found after the dollar-quoted string")
def step_verify_semi_after_dollar(context):
"""The unquoted semicolon is at index 7 (right after $$a;b$$)."""
assert context.semicolon_index == 7, f"Expected 7, got {context.semicolon_index}"
@given(
"SQL content with named dollar-quoted string containing a semicolon then real semicolon"
)
def step_sql_named_dollar_with_inner_semi(context):
"""Content: $t$a;b$t$;X — semicolon inside $t$ should be skipped."""
context.sql_content = "$t$a;b$t$;X"
@then("the semicolon should be found after the named dollar-quoted string")
def step_verify_semi_after_named_dollar(context):
"""The unquoted semicolon is at index 9 (right after $t$a;b$t$)."""
assert context.semicolon_index == 9, f"Expected 9, got {context.semicolon_index}"
# ---------------------------------------------------------------------------
# find_unquoted_semicolon — unterminated dollar-quoted (line 129 else branch)
# ---------------------------------------------------------------------------
@given("SQL content with an unterminated dollar-quoted string containing a semicolon")
def step_sql_unterminated_dollar_with_semi(context):
"""Content: $$a;b — the $$ never closes, so the ; inside is quoted."""
context.sql_content = "$$a;b"
@then("ssacov the result should be -1 indicating no unquoted semicolon")
def step_verify_no_semicolon(context):
"""No unquoted semicolon exists."""
assert context.semicolon_index == -1, f"Expected -1, got {context.semicolon_index}"
# ---------------------------------------------------------------------------
# strip_sql_comments — mix of dollar-quoted strings and comments
# ---------------------------------------------------------------------------
@given("SQL content mixing dollar-quoted strings and comments")
def step_sql_mixed(context):
"""Dollar-quoted string followed by a line comment and more SQL."""
context.sql_content = "$$body$$ -- this is a comment\nSELECT 1"
@then("only the comments should be removed and dollar strings preserved")
def step_verify_mixed(context):
"""The dollar-quoted string stays, the comment is removed."""
assert context.stripped_result == "$$body$$ \nSELECT 1", (
f"Got {context.stripped_result!r}"
)
# ---------------------------------------------------------------------------
# find_unquoted_semicolon with start offset
# ---------------------------------------------------------------------------
@when("I search for unquoted semicolon starting at offset {offset:d}")
def step_find_semicolon_with_offset(context, offset):
"""Call find_unquoted_semicolon with a start offset."""
context.semicolon_index = find_unquoted_semicolon(context.sql_content, offset)
@then("the semicolon should be found at index {index:d}")
def step_verify_semicolon_at_index(context, index):
"""Assert the semicolon was found at the expected index."""
assert context.semicolon_index == index, (
f"Expected {index}, got {context.semicolon_index}"
)