test: exact pytest.raises(IntegrityError) matcher, UPDATE/DELETE ban, unquote AST strings in probe guard

This commit is contained in:
Joshua King
2026-09-14 08:54:38 -04:00
parent 2265b5c3f1
commit 020b3679e1
+84 -37
View File
@@ -607,9 +607,18 @@ def test_dao_matrix_test_file_raw_probes_are_only_constraint_probes() -> None:
tree = ast.parse(text) tree = ast.parse(text)
probe_tables = { probe_tables = {
"test_matrix_children_crud_and_constraints": "children", "test_matrix_children_crud_and_constraints": ("children", "INSERT"),
"test_matrix_admin_users_crud_and_constraints": "admin_users", "test_matrix_admin_users_crud_and_constraints": (
"admin_users",
"INSERT",
),
} }
any_table = re.compile(
r"\b(children|admin_users)\b", re.IGNORECASE
)
allowed_stmt = re.compile(
r"^(INSERT|SELECT)", re.IGNORECASE
)
found_probes = 0 found_probes = 0
for node in ast.walk(tree): for node in ast.walk(tree):
if ( if (
@@ -617,12 +626,8 @@ def test_dao_matrix_test_file_raw_probes_are_only_constraint_probes() -> None:
or node.name not in probe_tables or node.name not in probe_tables
): ):
continue continue
table = probe_tables[node.name] table, expected_verb = probe_tables[node.name]
# Walk the function body; require each raw execute naming the raw_statements: list[tuple[ast.Await, str]] = []
# table to be lexically inside a pytest.raises with block, and
# require at least one such probe per sanctioned function.
raw_inserts: list[ast.Await] = []
raises_blocks: list[ast.With] = []
for sub in ast.walk(node): for sub in ast.walk(node):
if ( if (
isinstance(sub, ast.Await) isinstance(sub, ast.Await)
@@ -632,42 +637,84 @@ def test_dao_matrix_test_file_raw_probes_are_only_constraint_probes() -> None:
): ):
sql_arg = sub.value.args[0] if sub.value.args else None sql_arg = sub.value.args[0] if sub.value.args else None
rendered = ast.unparse(sql_arg) if sql_arg else "" rendered = ast.unparse(sql_arg) if sql_arg else ""
if re.search( raw_statements.append((sub, rendered))
r"INSERT\s+INTO\s+" + table, rendered, re.IGNORECASE # Every statement in the sanctioned functions whose SQL names
): # children/admin_users must be either (a) the sanctioned probe
raw_inserts.append(sub) # INSERT for that function's table, nested inside a
assert raw_inserts, ( # pytest.raises whose matcher names IntegrityError, or (b) an
f"{node.name} must contain its sanctioned raw {table} probe" # INSERT/SELECT against a DIFFERENT table. No UPDATE/DELETE.
) def _unquote(rendered: str) -> str:
# Collect the AST spans of every `with pytest.raises(...)` block rendered = rendered.strip()
# in the function; each probe must fall inside one. if len(rendered) >= 2 and rendered[0] == rendered[-1] and (
for sub in ast.walk(node): rendered[0] in "'\""
if (
isinstance(sub, ast.With)
and isinstance(sub.items[0].context_expr, ast.Call)
and isinstance(
sub.items[0].context_expr.func, ast.Attribute
)
and sub.items[0].context_expr.func.attr == "raises"
): ):
raises_blocks.append(sub) return rendered[1:-1]
for insert in raw_inserts: return rendered
inside = any(
raises_block.lineno <= insert.lineno for insert, rendered in raw_statements:
and insert.end_lineno <= raises_block.end_lineno rendered = _unquote(rendered)
for raises_block in raises_blocks if not any_table.search(rendered):
continue
verb_match = re.match(r"(\w+)", rendered.strip())
verb = verb_match.group(1).upper() if verb_match else ""
assert allowed_stmt.match(rendered.strip()) is not None, (
f"{node.name}: raw {table} table SQL "
f"must be INSERT or SELECT, got: {rendered[:80]!r}"
) )
assert inside, ( if re.search(rf"INSERT\s+INTO\s+{table}\b", rendered, re.I):
f"the raw {table} insert in {node.name} must be nested " # This is the sanctioned probe: require pytest.raises
"in a pytest.raises block (expected-to-fail constraint " # with an IntegrityError matcher around it.
"probe, not a successful write)" blocks = [
) (block, ast.unparse(block.items[0].context_expr.args))
found_probes += 1 for block in node_body_raises(node)
if block.items[0].context_expr.args
]
inside = any(
block.lineno <= insert.lineno
and insert.end_lineno <= block.end_lineno
and re.search(r"IntegrityError", matcher or "", re.I)
and "pytest" in ast.unparse(
block.items[0].context_expr.func
)
for block, matcher in blocks
)
assert inside, (
f"the raw {table} insert in {node.name} must be nested "
"in a pytest.raises(...IntegrityError...) block "
"(expected-to-fail constraint probe, not a write)"
)
found_probes += 1
# UPDATE/DELETE naming the tables anywhere in a sanctioned
# function is forbidden outright.
for _, rendered in raw_statements:
rendered = _unquote(rendered)
if any_table.search(rendered) and re.match(
r"(UPDATE|DELETE)", rendered.strip(), re.IGNORECASE
):
raise AssertionError(
f"{node.name} must not hold UPDATE/DELETE SQL for "
f"children/admin_users: {rendered[:80]!r}"
)
assert found_probes == 2, ( assert found_probes == 2, (
"exactly two sanctioned raw constraint probes must exist" "exactly two sanctioned raw constraint probes must exist"
) )
def node_body_raises(node):
"""Yield every ``with pytest.raises(...)`` block in ``node``."""
import ast
for sub in ast.walk(node):
if (
isinstance(sub, ast.With)
and sub.items
and isinstance(sub.items[0].context_expr, ast.Call)
and isinstance(sub.items[0].context_expr.func, ast.Attribute)
and sub.items[0].context_expr.func.attr == "raises"
):
yield sub
def test_dao_test_file_uses_dao_not_raw_table_sql() -> None: def test_dao_test_file_uses_dao_not_raw_table_sql() -> None:
"""This test module must exercise the DAO, not raw SQL, for these """This test module must exercise the DAO, not raw SQL, for these
tables. The two guard functions in this file necessarily mention tables. The two guard functions in this file necessarily mention