|
1 | | -"""Tests for T-SQL-specific rules (T001-T004).""" |
| 1 | +"""Tests for T-SQL-specific rules (T001-T006).""" |
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
5 | 5 | from sql_guard.rules.tsql import ( |
6 | 6 | CursorDeclaration, |
7 | 7 | DeprecatedOuterJoin, |
| 8 | + SelectStarInto, |
8 | 9 | WithNolock, |
9 | 10 | XpCmdshell, |
10 | 11 | ) |
@@ -138,3 +139,68 @@ def test_w002_still_accepts_fetch_first(): |
138 | 139 | rule = MissingLimit() |
139 | 140 | sql = "SELECT id FROM orders ORDER BY id FETCH FIRST 10 ROWS ONLY" |
140 | 141 | assert _check_statement(rule, sql) is None |
| 142 | + |
| 143 | + |
| 144 | +# T006 select-into-without-typed-fields |
| 145 | + |
| 146 | + |
| 147 | +def test_t006_flags_basic_select_star_into(): |
| 148 | + rule = SelectStarInto() |
| 149 | + finding = _check_statement(rule, "SELECT * INTO staging_orders FROM orders;") |
| 150 | + assert finding is not None |
| 151 | + assert finding.rule_id == "T006" |
| 152 | + assert finding.severity == "warning" |
| 153 | + |
| 154 | + |
| 155 | +def test_t006_flags_select_star_into_with_where(): |
| 156 | + rule = SelectStarInto() |
| 157 | + sql = "SELECT * INTO archive_2024 FROM orders WHERE year = 2024;" |
| 158 | + assert _check_statement(rule, sql) is not None |
| 159 | + |
| 160 | + |
| 161 | +def test_t006_flags_multiline_select_star_into(): |
| 162 | + rule = SelectStarInto() |
| 163 | + sql = "SELECT *\nINTO staging_orders\nFROM orders;" |
| 164 | + assert _check_statement(rule, sql) is not None |
| 165 | + |
| 166 | + |
| 167 | +def test_t006_case_insensitive(): |
| 168 | + rule = SelectStarInto() |
| 169 | + assert _check_statement(rule, "select * into staging from orders;") is not None |
| 170 | + |
| 171 | + |
| 172 | +def test_t006_does_not_flag_typed_columns(): |
| 173 | + # The recommended pass form from the issue. |
| 174 | + rule = SelectStarInto() |
| 175 | + sql = "SELECT order_id, customer_id INTO staging_orders FROM orders;" |
| 176 | + assert _check_statement(rule, sql) is None |
| 177 | + |
| 178 | + |
| 179 | +def test_t006_does_not_flag_single_column_into(): |
| 180 | + rule = SelectStarInto() |
| 181 | + assert _check_statement(rule, "SELECT id INTO ids FROM orders;") is None |
| 182 | + |
| 183 | + |
| 184 | +def test_t006_does_not_flag_select_star_without_into(): |
| 185 | + rule = SelectStarInto() |
| 186 | + assert _check_statement(rule, "SELECT * FROM orders WHERE id = 1;") is None |
| 187 | + |
| 188 | + |
| 189 | +def test_t006_does_not_flag_tsql_variable_assignment(): |
| 190 | + # SELECT @x = COUNT(*) FROM ... is a T-SQL local-variable assignment, |
| 191 | + # not a SELECT * INTO target. No schema is being inferred. |
| 192 | + rule = SelectStarInto() |
| 193 | + assert _check_statement(rule, "SELECT @x = COUNT(*) FROM orders;") is None |
| 194 | + |
| 195 | + |
| 196 | +def test_t006_does_not_flag_select_star_inside_cte(): |
| 197 | + rule = SelectStarInto() |
| 198 | + sql = "WITH s AS (SELECT * FROM orders) SELECT id FROM s;" |
| 199 | + assert _check_statement(rule, sql) is None |
| 200 | + |
| 201 | + |
| 202 | +def test_t006_message_mentions_runtime_schema(): |
| 203 | + rule = SelectStarInto() |
| 204 | + finding = _check_statement(rule, "SELECT * INTO staging FROM orders;") |
| 205 | + assert finding is not None |
| 206 | + assert "runtime" in finding.message.lower() or "source" in finding.message.lower() |
0 commit comments