forked from wherobots/wherobots-python-dbapi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_cursor.py
More file actions
154 lines (125 loc) · 6.21 KB
/
test_cursor.py
File metadata and controls
154 lines (125 loc) · 6.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""Tests for Cursor class behavior.
These tests verify that:
1. SQL queries containing literal percent signs (e.g., LIKE '%good') work
correctly regardless of whether parameters are provided.
2. Pyformat parameter substitution (%(name)s) works correctly.
3. Unknown parameter keys raise ProgrammingError.
"""
import pytest
from unittest.mock import MagicMock
from wherobots.db.cursor import Cursor, _substitute_parameters
from wherobots.db.errors import ProgrammingError
def _make_cursor():
"""Create a Cursor with a mock exec_fn that captures the SQL sent."""
captured = {}
def mock_exec_fn(sql, handler, store):
captured["sql"] = sql
return "exec-1"
mock_cancel_fn = MagicMock()
cursor = Cursor(mock_exec_fn, mock_cancel_fn)
return cursor, captured
class TestCursorExecuteParameterSubstitution:
"""Tests for pyformat parameter substitution in cursor.execute()."""
def test_like_percent_without_parameters(self):
"""A query with a LIKE '%...' pattern and no parameters should not
raise from Python's % string formatting."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM table WHERE name LIKE '%good'"
cursor.execute(sql)
assert captured["sql"] == sql
def test_like_percent_at_end_without_parameters(self):
"""A query with a trailing percent in LIKE should work without parameters."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM table WHERE name LIKE 'good%'"
cursor.execute(sql)
assert captured["sql"] == sql
def test_like_double_percent_without_parameters(self):
"""A query with percent on both sides in LIKE should work without parameters."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM table WHERE name LIKE '%good%'"
cursor.execute(sql)
assert captured["sql"] == sql
def test_multiple_percent_patterns_without_parameters(self):
"""A query with multiple LIKE clauses containing percents should work."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM t WHERE a LIKE '%foo%' AND b LIKE '%bar'"
cursor.execute(sql)
assert captured["sql"] == sql
def test_parameters_none_with_percent_in_query(self):
"""Explicitly passing parameters=None with a percent-containing query
should not raise."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM table WHERE name LIKE '%good'"
cursor.execute(sql, parameters=None)
assert captured["sql"] == sql
def test_empty_parameters_with_percent_in_query(self):
"""Passing an empty dict as parameters with a percent-containing query
should not raise."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM table WHERE name LIKE '%good'"
cursor.execute(sql, parameters={})
assert captured["sql"] == sql
def test_parameter_substitution_works(self):
"""Named pyformat parameter substitution should work correctly."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM table WHERE id = %(id)s"
cursor.execute(sql, parameters={"id": 42})
assert captured["sql"] == "SELECT * FROM table WHERE id = 42"
def test_multiple_parameters(self):
"""Multiple named parameters should all be substituted."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM t WHERE id = %(id)s AND name = %(name)s"
cursor.execute(sql, parameters={"id": 1, "name": "alice"})
assert captured["sql"] == "SELECT * FROM t WHERE id = 1 AND name = alice"
def test_like_with_parameters(self):
"""A LIKE expression with literal percent signs should work alongside
named parameters without requiring %% escaping."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM table WHERE name LIKE '%good%' AND id = %(id)s"
cursor.execute(sql, parameters={"id": 42})
assert captured["sql"] == (
"SELECT * FROM table WHERE name LIKE '%good%' AND id = 42"
)
def test_plain_query_without_parameters(self):
"""A simple query with no percent signs and no parameters should work."""
cursor, captured = _make_cursor()
sql = "SELECT * FROM table"
cursor.execute(sql)
assert captured["sql"] == sql
def test_unknown_parameter_raises(self):
"""Referencing a parameter key not in the dict should raise ProgrammingError."""
cursor, _ = _make_cursor()
sql = "SELECT * FROM table WHERE id = %(missing)s"
with pytest.raises(ProgrammingError, match="missing"):
cursor.execute(sql, parameters={"id": 42})
class TestSubstituteParameters:
"""Unit tests for the _substitute_parameters helper directly."""
def test_no_parameters_returns_operation_unchanged(self):
sql = "SELECT * FROM t WHERE name LIKE '%test%'"
assert _substitute_parameters(sql, None) == sql
def test_empty_dict_returns_operation_unchanged(self):
sql = "SELECT * FROM t WHERE name LIKE '%test%'"
assert _substitute_parameters(sql, {}) == sql
def test_substitutes_named_param(self):
sql = "SELECT * FROM t WHERE id = %(id)s"
assert _substitute_parameters(sql, {"id": 99}) == (
"SELECT * FROM t WHERE id = 99"
)
def test_preserves_literal_percent_with_params(self):
sql = "SELECT * FROM t WHERE name LIKE '%foo%' AND id = %(id)s"
assert _substitute_parameters(sql, {"id": 1}) == (
"SELECT * FROM t WHERE name LIKE '%foo%' AND id = 1"
)
def test_unknown_key_raises_programming_error(self):
sql = "SELECT * FROM t WHERE id = %(nope)s"
with pytest.raises(ProgrammingError, match="nope"):
_substitute_parameters(sql, {"id": 1})
def test_repeated_param_substituted_everywhere(self):
sql = "SELECT * FROM t WHERE a = %(v)s OR b = %(v)s"
assert _substitute_parameters(sql, {"v": 7}) == (
"SELECT * FROM t WHERE a = 7 OR b = 7"
)
def test_bare_percent_s_not_treated_as_param(self):
"""A bare %s (format-style, not pyformat) should be left untouched."""
sql = "SELECT * FROM t WHERE id = %s"
assert _substitute_parameters(sql, {"id": 1}) == sql