|
16 | 16 | import os |
17 | 17 | import shlex |
18 | 18 | import subprocess |
| 19 | +import sys |
19 | 20 | import tempfile |
20 | 21 | import tarfile |
21 | 22 | import shutil |
|
46 | 47 | from specify_cli.extensions import ExtensionRegistry |
47 | 48 | from specify_cli._console import console |
48 | 49 | from specify_cli.presets._commands import ( |
| 50 | + _render_powershell_argv, |
49 | 51 | _warn_unmet_extension_dependencies, |
50 | 52 | preset_update, |
51 | 53 | ) |
@@ -11650,12 +11652,69 @@ def fail_add(**_kwargs): |
11650 | 11652 | "6", |
11651 | 11653 | ] |
11652 | 11654 | expected = ( |
11653 | | - subprocess.list2cmdline(retry_args) |
| 11655 | + _render_powershell_argv(retry_args) |
11654 | 11656 | if os.name == "nt" |
11655 | 11657 | else shlex.join(retry_args) |
11656 | 11658 | ) |
11657 | 11659 | assert expected in output |
11658 | 11660 |
|
| 11661 | + def test_retry_command_quotes_powershell_metacharacters( |
| 11662 | + self, project_dir, monkeypatch, capsys |
| 11663 | + ): |
| 11664 | + """Windows retry commands keep PowerShell metacharacters literal.""" |
| 11665 | + commands = self._manager(monkeypatch, project_dir) |
| 11666 | + monkeypatch.setattr(commands, "preset_remove", lambda _preset_id: None) |
| 11667 | + |
| 11668 | + def fail_add(**_kwargs): |
| 11669 | + raise typer.Exit(1) |
| 11670 | + |
| 11671 | + monkeypatch.setattr(commands, "preset_add", fail_add) |
| 11672 | + monkeypatch.setattr(os, "name", "nt") |
| 11673 | + |
| 11674 | + with pytest.raises(typer.Exit) as exc_info: |
| 11675 | + preset_update( |
| 11676 | + "test-pack", |
| 11677 | + from_url=None, |
| 11678 | + dev=r"C:\replacement&$backup's presets", |
| 11679 | + priority=6, |
| 11680 | + ) |
| 11681 | + |
| 11682 | + assert exc_info.value.exit_code == 1 |
| 11683 | + output = strip_ansi(capsys.readouterr().out) |
| 11684 | + expected = ( |
| 11685 | + "& 'specify' 'preset' 'add' 'test-pack' '--dev' " |
| 11686 | + "'C:\\replacement&$backup''s presets' '--priority' '6'" |
| 11687 | + ) |
| 11688 | + assert "Retry in PowerShell: " in output |
| 11689 | + assert expected in output |
| 11690 | + |
| 11691 | + def test_powershell_retry_renderer_preserves_literal_arguments(self): |
| 11692 | + """The rendered command survives parsing by a real PowerShell.""" |
| 11693 | + powershell = shutil.which("pwsh") or shutil.which("powershell") |
| 11694 | + if powershell is None: |
| 11695 | + pytest.skip("PowerShell is not available") |
| 11696 | + |
| 11697 | + arguments = [ |
| 11698 | + "https://example.com/archive.zip?one=1&two=$value", |
| 11699 | + r"C:\owner's presets", |
| 11700 | + ] |
| 11701 | + rendered = _render_powershell_argv( |
| 11702 | + [ |
| 11703 | + sys.executable, |
| 11704 | + "-c", |
| 11705 | + "import json,sys; print(json.dumps(sys.argv[1:]))", |
| 11706 | + *arguments, |
| 11707 | + ] |
| 11708 | + ) |
| 11709 | + result = subprocess.run( |
| 11710 | + [powershell, "-NoProfile", "-Command", rendered], |
| 11711 | + check=True, |
| 11712 | + capture_output=True, |
| 11713 | + text=True, |
| 11714 | + ) |
| 11715 | + |
| 11716 | + assert json.loads(result.stdout) == arguments |
| 11717 | + |
11659 | 11718 | def test_invalid_priority_rejected_before_removal( |
11660 | 11719 | self, project_dir, monkeypatch, capsys |
11661 | 11720 | ): |
|
0 commit comments