Skip to content

Commit dfd74ae

Browse files
committed
fix(init): use Typer built-in help instead of hand-crafted usage panel
Replace hand-crafted usage text with ctx.get_help() to prevent drift when CLI options change. Update argument help text to clarify that '.' must be explicitly passed for current-directory init.
1 parent 0020f45 commit dfd74ae

2 files changed

Lines changed: 67 additions & 52 deletions

File tree

src/runpod_flash/cli/commands/init.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,17 @@
1414

1515

1616
def init_command(
17+
ctx: typer.Context,
1718
project_name: Optional[str] = typer.Argument(
18-
None, help="Project name or '.' for current directory"
19+
None, help="Project name, or '.' to initialize in current directory"
1920
),
2021
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"),
2122
):
2223
"""Create new Flash project with Flash Server and GPU workers."""
2324

2425
# No argument provided — show usage and exit
2526
if project_name is None:
26-
console.print(
27-
Panel(
28-
"[bold]Usage:[/bold]\n\n"
29-
" flash init [bold].[/bold] Initialize in current directory\n"
30-
" flash init [bold]<name>[/bold] Create new project in <name>/\n\n"
31-
"[bold]Options:[/bold]\n"
32-
" --force, -f Overwrite existing files\n\n"
33-
"[bold]Examples:[/bold]\n"
34-
" flash init my-project\n"
35-
" flash init .\n"
36-
" flash init my-project --force",
37-
title="flash init",
38-
expand=False,
39-
)
40-
)
27+
console.print(Panel(ctx.get_help(), title="flash init", expand=False))
4128
raise typer.Exit(0)
4229

4330
# Determine target directory and initialization mode

tests/unit/cli/commands/test_init.py

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99
from runpod_flash.cli.commands.init import init_command
1010

1111

12+
@pytest.fixture
13+
def mock_typer_ctx():
14+
"""Create a mock typer.Context for direct init_command calls."""
15+
ctx = MagicMock(spec=typer.Context)
16+
ctx.get_help.return_value = "Usage: flash init [OPTIONS] [PROJECT_NAME]"
17+
return ctx
18+
19+
1220
@pytest.fixture
1321
def mock_context(monkeypatch):
1422
"""Set up mocks for init command testing."""
@@ -46,11 +54,13 @@ def mock_context(monkeypatch):
4654
class TestInitCommandNewDirectory:
4755
"""Tests for init command when creating a new directory."""
4856

49-
def test_create_new_directory(self, mock_context, tmp_path, monkeypatch):
57+
def test_create_new_directory(
58+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
59+
):
5060
"""Test creating new project directory."""
5161
monkeypatch.chdir(tmp_path)
5262

53-
init_command("my_project")
63+
init_command(mock_typer_ctx, "my_project")
5464

5565
# Verify directory was created
5666
assert (tmp_path / "my_project").exists()
@@ -61,21 +71,25 @@ def test_create_new_directory(self, mock_context, tmp_path, monkeypatch):
6171
# Verify console output
6272
mock_context["console"].print.assert_called()
6373

64-
def test_create_nested_directory(self, mock_context, tmp_path, monkeypatch):
74+
def test_create_nested_directory(
75+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
76+
):
6577
"""Test creating project in nested directory structure."""
6678
monkeypatch.chdir(tmp_path)
6779

68-
init_command("path/to/my_project")
80+
init_command(mock_typer_ctx, "path/to/my_project")
6981

7082
# Verify nested directory was created
7183
assert (tmp_path / "path/to/my_project").exists()
7284

73-
def test_force_flag_skips_confirmation(self, mock_context, tmp_path, monkeypatch):
85+
def test_force_flag_skips_confirmation(
86+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
87+
):
7488
"""Test that force flag bypasses conflict prompts."""
7589
monkeypatch.chdir(tmp_path)
7690
mock_context["detect_conflicts"].return_value = ["main.py", "requirements.txt"]
7791

78-
init_command("my_project", force=True)
92+
init_command(mock_typer_ctx, "my_project", force=True)
7993

8094
# Verify skeleton was created
8195
mock_context["create_skeleton"].assert_called_once()
@@ -84,24 +98,24 @@ def test_force_flag_skips_confirmation(self, mock_context, tmp_path, monkeypatch
8498
class TestInitCommandNoArgs:
8599
"""Tests for init command when called with no arguments."""
86100

87-
def test_no_args_shows_help_and_exits(self, mock_context):
101+
def test_no_args_shows_help_and_exits(self, mock_typer_ctx, mock_context):
88102
"""flash init with no args should show help and exit."""
89103
with pytest.raises(typer.Exit) as exc_info:
90-
init_command(None)
104+
init_command(mock_typer_ctx, None)
91105

92106
assert exc_info.value.exit_code == 0
93107

94-
def test_no_args_does_not_create_skeleton(self, mock_context):
108+
def test_no_args_does_not_create_skeleton(self, mock_typer_ctx, mock_context):
95109
"""flash init with no args should not create project skeleton."""
96110
with pytest.raises(typer.Exit):
97-
init_command(None)
111+
init_command(mock_typer_ctx, None)
98112

99113
mock_context["create_skeleton"].assert_not_called()
100114

101-
def test_no_args_prints_usage_info(self, mock_context):
115+
def test_no_args_prints_usage_info(self, mock_typer_ctx, mock_context):
102116
"""flash init with no args should print usage information."""
103117
with pytest.raises(typer.Exit):
104-
init_command(None)
118+
init_command(mock_typer_ctx, None)
105119

106120
# Verify console.print was called with a Panel containing usage info
107121
mock_context["console"].print.assert_called_once()
@@ -114,11 +128,13 @@ class TestInitCommandCurrentDirectory:
114128
"""Tests for init command when using current directory."""
115129

116130
@patch("pathlib.Path.cwd")
117-
def test_init_current_directory_with_dot(self, mock_cwd, mock_context, tmp_path):
131+
def test_init_current_directory_with_dot(
132+
self, mock_cwd, mock_typer_ctx, mock_context, tmp_path
133+
):
118134
"""Test initialization in current directory with '.' argument."""
119135
mock_cwd.return_value = tmp_path
120136

121-
init_command(".")
137+
init_command(mock_typer_ctx, ".")
122138

123139
# Verify skeleton was created
124140
mock_context["create_skeleton"].assert_called_once()
@@ -127,21 +143,25 @@ def test_init_current_directory_with_dot(self, mock_cwd, mock_context, tmp_path)
127143
class TestInitCommandConflictDetection:
128144
"""Tests for init command file conflict detection and resolution."""
129145

130-
def test_no_conflicts_no_prompt(self, mock_context, tmp_path, monkeypatch):
146+
def test_no_conflicts_no_prompt(
147+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
148+
):
131149
"""Test that prompt is skipped when no conflicts exist."""
132150
monkeypatch.chdir(tmp_path)
133151
mock_context["detect_conflicts"].return_value = []
134152

135-
init_command("my_project")
153+
init_command(mock_typer_ctx, "my_project")
136154

137155
# Verify skeleton was created
138156
mock_context["create_skeleton"].assert_called_once()
139157

140-
def test_console_called_multiple_times(self, mock_context, tmp_path, monkeypatch):
158+
def test_console_called_multiple_times(
159+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
160+
):
141161
"""Test that console prints multiple outputs."""
142162
monkeypatch.chdir(tmp_path)
143163

144-
init_command("my_project")
164+
init_command(mock_typer_ctx, "my_project")
145165

146166
# Verify console.print was called multiple times
147167
assert mock_context["console"].print.call_count > 0
@@ -150,55 +170,63 @@ def test_console_called_multiple_times(self, mock_context, tmp_path, monkeypatch
150170
class TestInitCommandOutput:
151171
"""Tests for init command output messages."""
152172

153-
def test_panel_title_for_new_directory(self, mock_context, tmp_path, monkeypatch):
173+
def test_panel_title_for_new_directory(
174+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
175+
):
154176
"""Test that panel output is created for new directory."""
155177
monkeypatch.chdir(tmp_path)
156178

157-
init_command("my_project")
179+
init_command(mock_typer_ctx, "my_project")
158180

159181
# Verify console.print was called multiple times
160182
assert mock_context["console"].print.call_count > 0
161183

162184
@patch("pathlib.Path.cwd")
163-
def test_panel_title_for_current_directory(self, mock_cwd, mock_context, tmp_path):
185+
def test_panel_title_for_current_directory(
186+
self, mock_cwd, mock_typer_ctx, mock_context, tmp_path
187+
):
164188
"""Test that panel output is created for current directory."""
165189
mock_cwd.return_value = tmp_path
166190

167-
init_command(".")
191+
init_command(mock_typer_ctx, ".")
168192

169193
# Verify console.print was called
170194
assert mock_context["console"].print.call_count > 0
171195

172-
def test_next_steps_displayed(self, mock_context, tmp_path, monkeypatch):
196+
def test_next_steps_displayed(
197+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
198+
):
173199
"""Test next steps are displayed."""
174200
monkeypatch.chdir(tmp_path)
175201

176-
init_command("my_project")
202+
init_command(mock_typer_ctx, "my_project")
177203

178204
# Verify console.print was called with next steps text
179205
assert any(
180206
"Next steps" in str(c) for c in mock_context["console"].print.call_args_list
181207
)
182208

183209
@patch("pathlib.Path.cwd")
184-
def test_api_key_docs_link_displayed(self, mock_cwd, mock_context, tmp_path):
210+
def test_api_key_docs_link_displayed(
211+
self, mock_cwd, mock_typer_ctx, mock_context, tmp_path
212+
):
185213
"""Test API key documentation link is displayed."""
186214
mock_cwd.return_value = tmp_path
187215

188-
init_command(".")
216+
init_command(mock_typer_ctx, ".")
189217

190218
# Verify console.print was called with API key link
191219
assert any(
192220
"runpod.io" in str(c) for c in mock_context["console"].print.call_args_list
193221
)
194222

195223
def test_status_message_for_new_directory(
196-
self, mock_context, tmp_path, monkeypatch
224+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
197225
):
198226
"""Test status message while creating new directory."""
199227
monkeypatch.chdir(tmp_path)
200228

201-
init_command("my_project")
229+
init_command(mock_typer_ctx, "my_project")
202230

203231
# Check that status was called with appropriate message
204232
mock_context["console"].status.assert_called_once()
@@ -207,12 +235,12 @@ def test_status_message_for_new_directory(
207235

208236
@patch("pathlib.Path.cwd")
209237
def test_status_message_for_current_directory(
210-
self, mock_cwd, mock_context, tmp_path
238+
self, mock_cwd, mock_typer_ctx, mock_context, tmp_path
211239
):
212240
"""Test status message while initializing current directory."""
213241
mock_cwd.return_value = tmp_path
214242

215-
init_command(".")
243+
init_command(mock_typer_ctx, ".")
216244

217245
# Check that status was called with initialization message
218246
mock_context["console"].status.assert_called_once()
@@ -224,36 +252,36 @@ class TestInitCommandProjectNameHandling:
224252
"""Tests for project name handling."""
225253

226254
def test_special_characters_in_project_name(
227-
self, mock_context, tmp_path, monkeypatch
255+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
228256
):
229257
"""Test project name with special characters."""
230258
monkeypatch.chdir(tmp_path)
231259

232-
init_command("my-project_123")
260+
init_command(mock_typer_ctx, "my-project_123")
233261

234262
# Verify directory was created with the exact name
235263
assert (tmp_path / "my-project_123").exists()
236264

237265
def test_console_called_with_panels_and_tables(
238-
self, mock_context, tmp_path, monkeypatch
266+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
239267
):
240268
"""Test that console prints panels and tables."""
241269
monkeypatch.chdir(tmp_path)
242270

243-
init_command("test_project")
271+
init_command(mock_typer_ctx, "test_project")
244272

245273
# Verify console.print was called multiple times
246274
assert (
247275
mock_context["console"].print.call_count >= 4
248276
) # Panel, "Next steps:", Table, API key info
249277

250278
def test_directory_created_matches_argument(
251-
self, mock_context, tmp_path, monkeypatch
279+
self, mock_typer_ctx, mock_context, tmp_path, monkeypatch
252280
):
253281
"""Test that directory created matches the argument."""
254282
monkeypatch.chdir(tmp_path)
255283

256-
init_command("my_awesome_project")
284+
init_command(mock_typer_ctx, "my_awesome_project")
257285

258286
# Verify directory was created with exact name
259287
assert (tmp_path / "my_awesome_project").exists()

0 commit comments

Comments
 (0)