Skip to content

Commit cc17895

Browse files
committed
Add test coverage for client_query.py
A product of a recent refactor, client_query.py lacked specific tests.
1 parent 1e16b00 commit cc17895

2 files changed

Lines changed: 305 additions & 0 deletions

File tree

changelog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
Upcoming (TBD)
2+
==============
3+
4+
Internal
5+
--------
6+
* Add test coverage for `client_query.py`.
7+
8+
19
1.74.0 (2026/06/06)
210
==============
311

test/pytests/test_client_query.py

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
from types import SimpleNamespace
2+
from typing import Any, cast
3+
4+
from mycli import client_query, main
5+
from mycli.packages.sqlresult import SQLResult
6+
from mycli.types import Query
7+
from test.utils import ( # type: ignore[attr-defined]
8+
FakeCursorBase,
9+
ReusableLock,
10+
make_bare_mycli,
11+
)
12+
13+
14+
def make_refresh_cli() -> tuple[Any, dict[str, Any]]:
15+
cli = make_bare_mycli()
16+
state: dict[str, Any] = {
17+
'stopped': [],
18+
'refresh_calls': [],
19+
'set_dbname_calls': [],
20+
}
21+
callback = object()
22+
cli.schema_prefetcher = SimpleNamespace(stop=lambda: state['stopped'].append(True))
23+
cli.sqlexecute = SimpleNamespace(dbname='current')
24+
cli._on_completions_refreshed = callback
25+
cli.completer = SimpleNamespace(
26+
keyword_casing='upper',
27+
set_dbname=lambda dbname: state['set_dbname_calls'].append(dbname),
28+
)
29+
cli.main_formatter = SimpleNamespace(supported_formats=['ascii', 'csv'])
30+
cli.completion_refresher = SimpleNamespace(
31+
refresh=lambda executor, callbacks, options: state['refresh_calls'].append((executor, callbacks, options))
32+
)
33+
cli.smart_completion = True
34+
state['callback'] = callback
35+
return cli, state
36+
37+
38+
def test_refresh_completions_stops_prefetch() -> None:
39+
cli, state = make_refresh_cli()
40+
41+
main.MyCli.refresh_completions(cli)
42+
43+
assert state['stopped'] == [True]
44+
45+
46+
def test_refresh_completions_returns_started_status() -> None:
47+
cli, _state = make_refresh_cli()
48+
49+
result: list[SQLResult] = main.MyCli.refresh_completions(cli)
50+
51+
assert result == [SQLResult(status='Auto-completion refresh started in the background.')]
52+
53+
54+
def test_refresh_completions_passes_options_to_refresher() -> None:
55+
cli, state = make_refresh_cli()
56+
57+
main.MyCli.refresh_completions(cli)
58+
59+
assert state['refresh_calls'] == [
60+
(
61+
cli.sqlexecute,
62+
state['callback'],
63+
{
64+
'smart_completion': True,
65+
'supported_formats': ['ascii', 'csv'],
66+
'keyword_casing': 'upper',
67+
},
68+
)
69+
]
70+
71+
72+
def test_refresh_completions_does_not_update_dbname_without_reset() -> None:
73+
cli, state = make_refresh_cli()
74+
75+
main.MyCli.refresh_completions(cli)
76+
77+
assert state['set_dbname_calls'] == []
78+
79+
80+
def test_refresh_completions_updates_dbname_when_reset() -> None:
81+
cli = make_bare_mycli()
82+
set_dbname_calls: list[str] = []
83+
cli.schema_prefetcher = SimpleNamespace(stop=lambda: None)
84+
cli.sqlexecute = SimpleNamespace(dbname='next_db')
85+
cli.completer = SimpleNamespace(keyword_casing='lower', set_dbname=lambda dbname: set_dbname_calls.append(dbname))
86+
cli.main_formatter = SimpleNamespace(supported_formats=['table'])
87+
cli.completion_refresher = SimpleNamespace(refresh=lambda executor, callbacks, options: None)
88+
89+
main.MyCli.refresh_completions(cli, reset=True)
90+
91+
assert set_dbname_calls == ['next_db']
92+
93+
94+
def test_refresh_completions_uses_lock_when_reset() -> None:
95+
cli = make_bare_mycli()
96+
entered_lock = {'count': 0}
97+
cli.schema_prefetcher = SimpleNamespace(stop=lambda: None)
98+
cli.sqlexecute = SimpleNamespace(dbname='next_db')
99+
cli._completer_lock = cast(Any, ReusableLock(lambda: entered_lock.__setitem__('count', entered_lock['count'] + 1)))
100+
cli.completer = SimpleNamespace(keyword_casing='lower', set_dbname=lambda dbname: None)
101+
cli.main_formatter = SimpleNamespace(supported_formats=['table'])
102+
cli.completion_refresher = SimpleNamespace(refresh=lambda executor, callbacks, options: None)
103+
104+
main.MyCli.refresh_completions(cli, reset=True)
105+
106+
assert entered_lock == {'count': 1}
107+
108+
109+
def make_refreshed_cli() -> tuple[Any, Any, Any, dict[str, Any]]:
110+
cli = make_bare_mycli()
111+
state: dict[str, Any] = {
112+
'entered_lock': {'count': 0},
113+
'invalidated': [],
114+
'prefetch_started': [],
115+
'copy_calls': [],
116+
}
117+
old_completer = SimpleNamespace(dbmetadata={'old': object()})
118+
new_completer = SimpleNamespace(
119+
dbname='current',
120+
copy_other_schemas_from=lambda source, exclude: state['copy_calls'].append((source, exclude)),
121+
)
122+
cli._completer_lock = cast(
123+
Any,
124+
ReusableLock(lambda: state['entered_lock'].__setitem__('count', state['entered_lock']['count'] + 1)),
125+
)
126+
cli.completer = old_completer
127+
cli.prompt_session = SimpleNamespace(app=SimpleNamespace(invalidate=lambda: state['invalidated'].append(True)))
128+
cli.schema_prefetcher = SimpleNamespace(start_configured=lambda: state['prefetch_started'].append(True))
129+
return cli, old_completer, new_completer, state
130+
131+
132+
def test_on_completions_refreshed_swaps_completer() -> None:
133+
cli, _old_completer, new_completer, _state = make_refreshed_cli()
134+
135+
main.MyCli._on_completions_refreshed(cli, new_completer)
136+
137+
assert cli.completer is new_completer
138+
139+
140+
def test_on_completions_refreshed_copies_other_schemas() -> None:
141+
cli, old_completer, new_completer, state = make_refreshed_cli()
142+
143+
main.MyCli._on_completions_refreshed(cli, new_completer)
144+
145+
assert state['copy_calls'] == [(old_completer, 'current')]
146+
147+
148+
def test_on_completions_refreshed_uses_lock() -> None:
149+
cli, _old_completer, new_completer, state = make_refreshed_cli()
150+
151+
main.MyCli._on_completions_refreshed(cli, new_completer)
152+
153+
assert state['entered_lock'] == {'count': 1}
154+
155+
156+
def test_on_completions_refreshed_invalidates_prompt() -> None:
157+
cli, _old_completer, new_completer, state = make_refreshed_cli()
158+
159+
main.MyCli._on_completions_refreshed(cli, new_completer)
160+
161+
assert state['invalidated'] == [True]
162+
163+
164+
def test_on_completions_refreshed_starts_schema_prefetch() -> None:
165+
cli, _old_completer, new_completer, state = make_refreshed_cli()
166+
167+
main.MyCli._on_completions_refreshed(cli, new_completer)
168+
169+
assert state['prefetch_started'] == [True]
170+
171+
172+
def run_query_with_state(monkeypatch, tmp_path, *, warnings_enabled: bool = True) -> dict[str, Any]:
173+
cli = make_bare_mycli()
174+
normal_rows = FakeCursorBase(rows=[('one',)], warning_count=1)
175+
warning_rows = FakeCursorBase(rows=[('warning',)], warning_count=0)
176+
state: dict[str, Any] = {
177+
'run_calls': [],
178+
'logged_queries': [],
179+
'logged_output': [],
180+
'formatted': [],
181+
'echoed': [],
182+
'checkpoint_path': tmp_path / 'checkpoint.sql',
183+
}
184+
185+
def run(sql: str) -> list[SQLResult]:
186+
state['run_calls'].append(sql)
187+
if sql == 'SHOW WARNINGS':
188+
return [SQLResult(rows=warning_rows, status='warnings')]
189+
return [SQLResult(rows=normal_rows, status='ok')]
190+
191+
def format_sqlresult(result: SQLResult, **kwargs: Any) -> list[str]:
192+
state['formatted'].append((result, kwargs))
193+
return [str(result.status)]
194+
195+
monkeypatch.setattr(client_query, 'Cursor', FakeCursorBase)
196+
monkeypatch.setattr(client_query.special, 'is_expanded_output', lambda: True)
197+
monkeypatch.setattr(client_query.special, 'is_redirected', lambda: False)
198+
monkeypatch.setattr(client_query.special, 'is_show_warnings_enabled', lambda: warnings_enabled)
199+
monkeypatch.setattr(client_query.click, 'echo', lambda line, nl=True: state['echoed'].append((line, nl)))
200+
201+
cli.sqlexecute = SimpleNamespace(run=run)
202+
cli.log_query = lambda query: state['logged_queries'].append(query)
203+
cli.log_output = lambda line: state['logged_output'].append(line)
204+
cli.format_sqlresult = format_sqlresult
205+
checkpoint = state['checkpoint_path'].open('w+', encoding='utf-8')
206+
try:
207+
main.MyCli.run_query(cli, 'select 1;\n', checkpoint=checkpoint, new_line=False)
208+
finally:
209+
checkpoint.close()
210+
state['cli'] = cli
211+
return state
212+
213+
214+
def test_run_query_executes_query(monkeypatch, tmp_path) -> None:
215+
state = run_query_with_state(monkeypatch, tmp_path, warnings_enabled=False)
216+
217+
assert state['run_calls'] == ['select 1;\n']
218+
219+
220+
def test_run_query_logs_query(monkeypatch, tmp_path) -> None:
221+
state = run_query_with_state(monkeypatch, tmp_path, warnings_enabled=False)
222+
223+
assert state['logged_queries'] == ['select 1;\n']
224+
225+
226+
def test_run_query_logs_output(monkeypatch, tmp_path) -> None:
227+
state = run_query_with_state(monkeypatch, tmp_path, warnings_enabled=False)
228+
229+
assert state['logged_output'] == ['ok']
230+
231+
232+
def test_run_query_echoes_output(monkeypatch, tmp_path) -> None:
233+
state = run_query_with_state(monkeypatch, tmp_path, warnings_enabled=False)
234+
235+
assert state['echoed'] == [('ok', False)]
236+
237+
238+
def test_run_query_sets_formatter_query(monkeypatch, tmp_path) -> None:
239+
state = run_query_with_state(monkeypatch, tmp_path, warnings_enabled=False)
240+
cli = state['cli']
241+
242+
assert (cli.main_formatter.query, cli.redirect_formatter.query) == ('select 1;\n', 'select 1;\n')
243+
244+
245+
def test_run_query_passes_display_flags_to_formatter(monkeypatch, tmp_path) -> None:
246+
state = run_query_with_state(monkeypatch, tmp_path, warnings_enabled=False)
247+
248+
assert state['formatted'][0][1]['is_expanded'] is True
249+
250+
251+
def test_run_query_uses_redirect_state_for_formatter(monkeypatch, tmp_path) -> None:
252+
state = run_query_with_state(monkeypatch, tmp_path, warnings_enabled=False)
253+
254+
assert state['formatted'][0][1]['is_redirected'] is False
255+
256+
257+
def test_run_query_does_not_style_regular_output_as_warnings(monkeypatch, tmp_path) -> None:
258+
state = run_query_with_state(monkeypatch, tmp_path, warnings_enabled=False)
259+
260+
assert state['formatted'][0][1].get('is_warnings_style') is not True
261+
262+
263+
def test_run_query_fetches_warnings_when_enabled(monkeypatch, tmp_path) -> None:
264+
state = run_query_with_state(monkeypatch, tmp_path)
265+
266+
assert state['run_calls'] == ['select 1;\n', 'SHOW WARNINGS']
267+
268+
269+
def test_run_query_styles_warning_output(monkeypatch, tmp_path) -> None:
270+
state = run_query_with_state(monkeypatch, tmp_path)
271+
272+
assert state['formatted'][1][1]['is_warnings_style'] is True
273+
274+
275+
def test_run_query_echoes_warning_output(monkeypatch, tmp_path) -> None:
276+
state = run_query_with_state(monkeypatch, tmp_path)
277+
278+
assert state['echoed'][-1] == ('warnings', False)
279+
280+
281+
def test_run_query_writes_checkpoint(monkeypatch, tmp_path) -> None:
282+
state = run_query_with_state(monkeypatch, tmp_path, warnings_enabled=False)
283+
284+
assert state['checkpoint_path'].read_text(encoding='utf-8') == 'select 1;\n'
285+
286+
287+
def test_get_last_query_returns_none() -> None:
288+
cli = make_bare_mycli()
289+
290+
assert main.MyCli.get_last_query(cli) is None
291+
292+
293+
def test_get_last_query_returns_latest_query() -> None:
294+
cli = make_bare_mycli()
295+
cli.query_history = [Query('select 1', True, False), Query('select 2', True, False)]
296+
297+
assert main.MyCli.get_last_query(cli) == 'select 2'

0 commit comments

Comments
 (0)