|
8 | 8 | import shutil |
9 | 9 | from tempfile import NamedTemporaryFile |
10 | 10 | from textwrap import dedent |
| 11 | +from types import SimpleNamespace |
| 12 | +from typing import Any, cast |
11 | 13 |
|
12 | 14 | import click |
13 | 15 | from click.testing import CliRunner |
14 | 16 | from pymysql.err import OperationalError |
| 17 | +import pytest |
15 | 18 |
|
| 19 | +from mycli import main |
16 | 20 | from mycli.constants import ( |
17 | 21 | DEFAULT_DATABASE, |
18 | 22 | DEFAULT_HOST, |
|
26 | 30 | from mycli.packages.special.main import COMMANDS as SPECIAL_COMMANDS |
27 | 31 | from mycli.packages.sqlresult import SQLResult |
28 | 32 | from mycli.sqlexecute import ServerInfo, SQLExecute |
29 | | -from test.utils import DATABASE, HOST, PASSWORD, PORT, TEMPFILE_PREFIX, USER, dbtest, run |
| 33 | +from test.utils import ( |
| 34 | + DATABASE, |
| 35 | + HOST, |
| 36 | + PASSWORD, |
| 37 | + PORT, |
| 38 | + TEMPFILE_PREFIX, |
| 39 | + USER, |
| 40 | + ReusableLock, |
| 41 | + call_click_entrypoint_direct, |
| 42 | + dbtest, |
| 43 | + make_bare_mycli, |
| 44 | + make_dummy_mycli_class, |
| 45 | + run, |
| 46 | +) |
30 | 47 |
|
31 | 48 | pytests_dir = os.path.abspath(os.path.dirname(__file__)) |
32 | 49 | project_root_dir = os.path.abspath(os.path.join(pytests_dir, '..', '..')) |
@@ -2150,3 +2167,160 @@ def test_null_string_config(monkeypatch): |
2150 | 2167 | os.remove(myclirc.name) |
2151 | 2168 | except Exception as e: |
2152 | 2169 | print(f'An error occurred while attempting to delete the file: {e}') |
| 2170 | + |
| 2171 | + |
| 2172 | +def test_change_prompt_format_requires_argument() -> None: |
| 2173 | + cli = make_bare_mycli() |
| 2174 | + assert main.MyCli.change_prompt_format(cli, '')[0].status == 'Missing required argument, format.' |
| 2175 | + |
| 2176 | + |
| 2177 | +def test_change_prompt_format_updates_prompt() -> None: |
| 2178 | + cli = make_bare_mycli() |
| 2179 | + assert main.MyCli.change_prompt_format(cli, '\\u@\\h> ')[0].status == 'Changed prompt format to \\u@\\h> ' |
| 2180 | + |
| 2181 | + |
| 2182 | +def test_output_timing_logs_and_prints_with_warning_style(monkeypatch: pytest.MonkeyPatch) -> None: |
| 2183 | + cli = make_bare_mycli() |
| 2184 | + timings_logged: list[str] = [] |
| 2185 | + cli.log_output = lambda text: timings_logged.append(text) # type: ignore[assignment] |
| 2186 | + printed: list[tuple[Any, Any]] = [] |
| 2187 | + monkeypatch.setattr(main, 'print_formatted_text', lambda text, style=None: printed.append((text, style))) |
| 2188 | + main.MyCli.output_timing(cli, 'Time: 1.000s', is_warnings_style=True) |
| 2189 | + assert timings_logged == ['Time: 1.000s'] |
| 2190 | + assert printed[-1][1] == cli.ptoolkit_style |
| 2191 | + |
| 2192 | + |
| 2193 | +def test_run_cli_delegates_to_main_repl(monkeypatch: pytest.MonkeyPatch) -> None: |
| 2194 | + cli = make_bare_mycli() |
| 2195 | + run_cli_calls: list[Any] = [] |
| 2196 | + monkeypatch.setattr(main, 'main_repl', lambda target: run_cli_calls.append(target)) |
| 2197 | + main.MyCli.run_cli(cli) |
| 2198 | + assert run_cli_calls == [cli] |
| 2199 | + |
| 2200 | + |
| 2201 | +def test_get_output_margin_uses_prompt_session_render_counter(monkeypatch: pytest.MonkeyPatch) -> None: |
| 2202 | + cli = make_bare_mycli() |
| 2203 | + render_counters: list[int] = [] |
| 2204 | + cli.prompt_lines = 0 |
| 2205 | + cli.get_reserved_space = lambda: 2 # type: ignore[assignment] |
| 2206 | + cli.prompt_session = cast( |
| 2207 | + Any, |
| 2208 | + SimpleNamespace(app=SimpleNamespace(render_counter=7)), |
| 2209 | + ) |
| 2210 | + |
| 2211 | + def fake_get_prompt(mycli: Any, string: str, render_counter: int) -> str: |
| 2212 | + render_counters.append(render_counter) |
| 2213 | + return 'line1\nline2' |
| 2214 | + |
| 2215 | + monkeypatch.setattr(main, 'get_prompt', fake_get_prompt) |
| 2216 | + monkeypatch.setattr(main.special, 'is_timing_enabled', lambda: False) |
| 2217 | + assert main.MyCli.get_output_margin(cli, 'ok') == 5 |
| 2218 | + assert render_counters == [7] |
| 2219 | + |
| 2220 | + |
| 2221 | +def test_on_completions_refreshed_updates_completer_and_invalidates_prompt() -> None: |
| 2222 | + cli = make_bare_mycli() |
| 2223 | + entered_lock = {'count': 0} |
| 2224 | + invalidated: list[bool] = [] |
| 2225 | + cli._completer_lock = cast(Any, ReusableLock(lambda: entered_lock.__setitem__('count', entered_lock['count'] + 1))) |
| 2226 | + cli.prompt_session = cast(Any, SimpleNamespace(app=SimpleNamespace(invalidate=lambda: invalidated.append(True)))) |
| 2227 | + new_completer = cast(Any, SimpleNamespace(get_completions=lambda document, event: ['done'])) |
| 2228 | + main.MyCli._on_completions_refreshed(cli, new_completer) |
| 2229 | + assert cli.completer is new_completer |
| 2230 | + assert invalidated == [True] |
| 2231 | + assert entered_lock['count'] == 1 |
| 2232 | + |
| 2233 | + |
| 2234 | +def test_get_completions_uses_current_completer() -> None: |
| 2235 | + cli = make_bare_mycli() |
| 2236 | + entered_lock = {'count': 0} |
| 2237 | + cli._completer_lock = cast(Any, ReusableLock(lambda: entered_lock.__setitem__('count', entered_lock['count'] + 1))) |
| 2238 | + cli.completer = cast(Any, SimpleNamespace(get_completions=lambda document, event: ['done'])) |
| 2239 | + assert list(main.MyCli.get_completions(cli, 'select', 6)) == ['done'] |
| 2240 | + assert entered_lock['count'] == 1 |
| 2241 | + |
| 2242 | + |
| 2243 | +def test_click_entrypoint_callback_covers_dsn_list_init_commands(monkeypatch: pytest.MonkeyPatch) -> None: |
| 2244 | + dummy_class = make_dummy_mycli_class( |
| 2245 | + config={ |
| 2246 | + 'main': {'use_keyring': 'false', 'my_cnf_transition_done': 'true'}, |
| 2247 | + 'connection': {'default_keepalive_ticks': 0}, |
| 2248 | + 'alias_dsn': {'prod': 'mysql://u:p@h/db'}, |
| 2249 | + 'alias_dsn.init-commands': {'prod': ['set a=1', 'set b=2']}, |
| 2250 | + } |
| 2251 | + ) |
| 2252 | + monkeypatch.setattr(main, 'MyCli', dummy_class) |
| 2253 | + monkeypatch.setattr(main.sys, 'stdin', SimpleNamespace(isatty=lambda: True)) |
| 2254 | + monkeypatch.setattr(main.sys.stderr, 'isatty', lambda: True) |
| 2255 | + |
| 2256 | + cli_args = main.CliArgs() |
| 2257 | + cli_args.dsn = 'prod' |
| 2258 | + cli_args.init_command = 'set c=3' |
| 2259 | + call_click_entrypoint_direct(cli_args) |
| 2260 | + |
| 2261 | + dummy = dummy_class.last_instance |
| 2262 | + assert dummy is not None |
| 2263 | + assert dummy.connect_calls[-1]['init_command'] == 'set a=1; set b=2; set c=3' |
| 2264 | + |
| 2265 | + |
| 2266 | +def test_click_entrypoint_callback_uses_batch_with_progress_path(monkeypatch: pytest.MonkeyPatch) -> None: |
| 2267 | + dummy_class = make_dummy_mycli_class( |
| 2268 | + config={ |
| 2269 | + 'main': {'use_keyring': 'false', 'my_cnf_transition_done': 'true'}, |
| 2270 | + 'connection': {'default_keepalive_ticks': 0}, |
| 2271 | + 'alias_dsn': {}, |
| 2272 | + } |
| 2273 | + ) |
| 2274 | + monkeypatch.setattr(main, 'MyCli', dummy_class) |
| 2275 | + monkeypatch.setattr(main.sys, 'stdin', SimpleNamespace(isatty=lambda: True)) |
| 2276 | + monkeypatch.setattr(main.sys.stderr, 'isatty', lambda: True) |
| 2277 | + monkeypatch.setattr(main, 'main_batch_with_progress_bar', lambda mycli, cli_args: 12) |
| 2278 | + |
| 2279 | + cli_args = main.CliArgs() |
| 2280 | + cli_args.batch = 'queries.sql' |
| 2281 | + cli_args.progress = True |
| 2282 | + with pytest.raises(SystemExit) as excinfo: |
| 2283 | + call_click_entrypoint_direct(cli_args) |
| 2284 | + assert excinfo.value.code == 12 |
| 2285 | + |
| 2286 | + |
| 2287 | +def test_click_entrypoint_callback_uses_batch_without_progress_path(monkeypatch: pytest.MonkeyPatch) -> None: |
| 2288 | + dummy_class = make_dummy_mycli_class( |
| 2289 | + config={ |
| 2290 | + 'main': {'use_keyring': 'false', 'my_cnf_transition_done': 'true'}, |
| 2291 | + 'connection': {'default_keepalive_ticks': 0}, |
| 2292 | + 'alias_dsn': {}, |
| 2293 | + } |
| 2294 | + ) |
| 2295 | + monkeypatch.setattr(main, 'MyCli', dummy_class) |
| 2296 | + monkeypatch.setattr(main.sys, 'stdin', SimpleNamespace(isatty=lambda: True)) |
| 2297 | + monkeypatch.setattr(main.sys.stderr, 'isatty', lambda: True) |
| 2298 | + monkeypatch.setattr(main, 'main_batch_without_progress_bar', lambda mycli, cli_args: 13) |
| 2299 | + |
| 2300 | + cli_args = main.CliArgs() |
| 2301 | + cli_args.batch = 'queries.sql' |
| 2302 | + cli_args.progress = False |
| 2303 | + with pytest.raises(SystemExit) as excinfo: |
| 2304 | + call_click_entrypoint_direct(cli_args) |
| 2305 | + assert excinfo.value.code == 13 |
| 2306 | + |
| 2307 | + |
| 2308 | +def test_click_entrypoint_callback_covers_mycnf_underscore_fallback(monkeypatch: pytest.MonkeyPatch) -> None: |
| 2309 | + click_lines: list[str] = [] |
| 2310 | + monkeypatch.setattr(click, 'secho', lambda message='', **kwargs: click_lines.append(str(message))) |
| 2311 | + monkeypatch.setattr(main.sys, 'stdin', SimpleNamespace(isatty=lambda: True)) |
| 2312 | + monkeypatch.setattr(main.sys.stderr, 'isatty', lambda: False) |
| 2313 | + |
| 2314 | + dummy_class = make_dummy_mycli_class( |
| 2315 | + config={ |
| 2316 | + 'main': {'use_keyring': 'false', 'my_cnf_transition_done': 'false'}, |
| 2317 | + 'connection': {'default_keepalive_ticks': 0}, |
| 2318 | + 'alias_dsn': {}, |
| 2319 | + }, |
| 2320 | + my_cnf={'client': {'ssl_ca': '/tmp/ca.pem'}, 'mysqld': {}}, |
| 2321 | + config_without_package_defaults={'main': {}}, |
| 2322 | + ) |
| 2323 | + monkeypatch.setattr(main, 'MyCli', dummy_class) |
| 2324 | + |
| 2325 | + call_click_entrypoint_direct(main.CliArgs()) |
| 2326 | + assert any('ssl-ca = /tmp/ca.pem' in line for line in click_lines) |
0 commit comments