forked from HAL9000/cleveragents-core
051ee7c290
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
216 lines
9.0 KiB
Python
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}"
|
|
)
|