33from unittest .mock import MagicMock , Mock , patch
44
55import pytest
6+ import typer
67
78from runpod_flash .cli .commands .init import init_command
89
910
11+ @pytest .fixture
12+ def mock_typer_ctx ():
13+ """Create a mock typer.Context for direct init_command calls."""
14+ ctx = MagicMock (spec = typer .Context )
15+ ctx .get_help .return_value = "Usage: flash init [OPTIONS] [PROJECT_NAME]"
16+ return ctx
17+
18+
1019@pytest .fixture
1120def mock_context (monkeypatch ):
1221 """Set up mocks for init command testing."""
@@ -44,11 +53,13 @@ def mock_context(monkeypatch):
4453class TestInitCommandNewDirectory :
4554 """Tests for init command when creating a new directory."""
4655
47- def test_create_new_directory (self , mock_context , tmp_path , monkeypatch ):
56+ def test_create_new_directory (
57+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
58+ ):
4859 """Test creating new project directory."""
4960 monkeypatch .chdir (tmp_path )
5061
51- init_command ("my_project" )
62+ init_command (mock_typer_ctx , "my_project" )
5263
5364 # Verify directory was created
5465 assert (tmp_path / "my_project" ).exists ()
@@ -59,45 +70,87 @@ def test_create_new_directory(self, mock_context, tmp_path, monkeypatch):
5970 # Verify console output
6071 mock_context ["console" ].print .assert_called ()
6172
62- def test_create_nested_directory (self , mock_context , tmp_path , monkeypatch ):
73+ def test_create_nested_directory (
74+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
75+ ):
6376 """Test creating project in nested directory structure."""
6477 monkeypatch .chdir (tmp_path )
6578
66- init_command ("path/to/my_project" )
79+ init_command (mock_typer_ctx , "path/to/my_project" )
6780
6881 # Verify nested directory was created
6982 assert (tmp_path / "path/to/my_project" ).exists ()
7083
71- def test_force_flag_skips_confirmation (self , mock_context , tmp_path , monkeypatch ):
84+ def test_force_flag_skips_confirmation (
85+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
86+ ):
7287 """Test that force flag bypasses conflict prompts."""
7388 monkeypatch .chdir (tmp_path )
7489 mock_context ["detect_conflicts" ].return_value = ["main.py" , "requirements.txt" ]
7590
76- init_command ("my_project" , force = True )
91+ init_command (mock_typer_ctx , "my_project" , force = True )
7792
7893 # Verify skeleton was created
7994 mock_context ["create_skeleton" ].assert_called_once ()
8095
8196
82- class TestInitCommandCurrentDirectory :
83- """Tests for init command when using current directory ."""
97+ class TestInitCommandNoArgs :
98+ """Tests for init command when called with no arguments ."""
8499
85- @ patch ( "pathlib.Path.cwd" )
86- def test_init_current_directory_with_none ( self , mock_cwd , mock_context , tmp_path ):
87- """Test initialization in current directory with None argument."""
88- mock_cwd . return_value = tmp_path
100+ def test_no_args_shows_help_and_exits ( self , mock_typer_ctx , mock_context ):
101+ """flash init with no args should show help and exit."""
102+ with pytest . raises ( typer . Exit ) as exc_info :
103+ init_command ( mock_typer_ctx , None )
89104
90- init_command (None )
105+ assert exc_info .value .exit_code == 0
106+
107+ def test_no_args_does_not_create_skeleton (self , mock_typer_ctx , mock_context ):
108+ """flash init with no args should not create project skeleton."""
109+ with pytest .raises (typer .Exit ):
110+ init_command (mock_typer_ctx , None )
111+
112+ mock_context ["create_skeleton" ].assert_not_called ()
113+
114+ def test_no_args_prints_usage_info (self , mock_typer_ctx , mock_context ):
115+ """flash init with no args should print usage information."""
116+ with pytest .raises (typer .Exit ):
117+ init_command (mock_typer_ctx , None )
118+
119+ # Verify ctx.get_help() was called and passed to console.print
120+ mock_typer_ctx .get_help .assert_called_once ()
121+ mock_context ["console" ].print .assert_called_once ()
122+ help_arg = mock_context ["console" ].print .call_args [0 ][0 ]
123+ assert "flash init" in help_arg
124+ kwargs = mock_context ["console" ].print .call_args [1 ]
125+ assert kwargs .get ("markup" ) is False
126+ assert kwargs .get ("highlight" ) is False
91127
92- # Verify skeleton was created
93- mock_context ["create_skeleton" ].assert_called_once ()
128+
129+ class TestInitCommandCliRunner :
130+ """CLI-level test to verify Typer context injection works end-to-end."""
131+
132+ def test_no_args_via_cli (self ):
133+ """flash init with no args should show help and exit 0 via CLI."""
134+ from typer .testing import CliRunner
135+
136+ from runpod_flash .cli .main import app
137+
138+ result = CliRunner ().invoke (app , ["init" ])
139+ assert result .exit_code == 0
140+ assert "flash init" in result .output .lower ()
141+
142+
143+ class TestInitCommandCurrentDirectory :
144+ """Tests for init command when using current directory."""
94145
95146 @patch ("pathlib.Path.cwd" )
96- def test_init_current_directory_with_dot (self , mock_cwd , mock_context , tmp_path ):
147+ def test_init_current_directory_with_dot (
148+ self , mock_cwd , mock_typer_ctx , mock_context , tmp_path
149+ ):
97150 """Test initialization in current directory with '.' argument."""
98151 mock_cwd .return_value = tmp_path
99152
100- init_command ("." )
153+ init_command (mock_typer_ctx , "." )
101154
102155 # Verify skeleton was created
103156 mock_context ["create_skeleton" ].assert_called_once ()
@@ -106,21 +159,25 @@ def test_init_current_directory_with_dot(self, mock_cwd, mock_context, tmp_path)
106159class TestInitCommandConflictDetection :
107160 """Tests for init command file conflict detection and resolution."""
108161
109- def test_no_conflicts_no_prompt (self , mock_context , tmp_path , monkeypatch ):
162+ def test_no_conflicts_no_prompt (
163+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
164+ ):
110165 """Test that prompt is skipped when no conflicts exist."""
111166 monkeypatch .chdir (tmp_path )
112167 mock_context ["detect_conflicts" ].return_value = []
113168
114- init_command ("my_project" )
169+ init_command (mock_typer_ctx , "my_project" )
115170
116171 # Verify skeleton was created
117172 mock_context ["create_skeleton" ].assert_called_once ()
118173
119- def test_console_called_multiple_times (self , mock_context , tmp_path , monkeypatch ):
174+ def test_console_called_multiple_times (
175+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
176+ ):
120177 """Test that console prints multiple outputs."""
121178 monkeypatch .chdir (tmp_path )
122179
123- init_command ("my_project" )
180+ init_command (mock_typer_ctx , "my_project" )
124181
125182 # Verify console.print was called multiple times
126183 assert mock_context ["console" ].print .call_count > 0
@@ -129,42 +186,50 @@ def test_console_called_multiple_times(self, mock_context, tmp_path, monkeypatch
129186class TestInitCommandOutput :
130187 """Tests for init command output messages."""
131188
132- def test_panel_title_for_new_directory (self , mock_context , tmp_path , monkeypatch ):
189+ def test_panel_title_for_new_directory (
190+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
191+ ):
133192 """Test that panel output is created for new directory."""
134193 monkeypatch .chdir (tmp_path )
135194
136- init_command ("my_project" )
195+ init_command (mock_typer_ctx , "my_project" )
137196
138197 # Verify console.print was called multiple times
139198 assert mock_context ["console" ].print .call_count > 0
140199
141200 @patch ("pathlib.Path.cwd" )
142- def test_panel_title_for_current_directory (self , mock_cwd , mock_context , tmp_path ):
201+ def test_panel_title_for_current_directory (
202+ self , mock_cwd , mock_typer_ctx , mock_context , tmp_path
203+ ):
143204 """Test that panel output is created for current directory."""
144205 mock_cwd .return_value = tmp_path
145206
146- init_command ("." )
207+ init_command (mock_typer_ctx , "." )
147208
148209 # Verify console.print was called
149210 assert mock_context ["console" ].print .call_count > 0
150211
151- def test_next_steps_displayed (self , mock_context , tmp_path , monkeypatch ):
212+ def test_next_steps_displayed (
213+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
214+ ):
152215 """Test next steps are displayed."""
153216 monkeypatch .chdir (tmp_path )
154217
155- init_command ("my_project" )
218+ init_command (mock_typer_ctx , "my_project" )
156219
157220 # Verify console.print was called with next steps text
158221 assert any (
159222 "Next steps" in str (c ) for c in mock_context ["console" ].print .call_args_list
160223 )
161224
162225 @patch ("pathlib.Path.cwd" )
163- def test_flash_login_step_displayed (self , mock_cwd , mock_context , tmp_path ):
226+ def test_flash_login_step_displayed (
227+ self , mock_cwd , mock_typer_ctx , mock_context , tmp_path
228+ ):
164229 """Test flash login is shown in the next steps table."""
165230 mock_cwd .return_value = tmp_path
166231
167- init_command ("." )
232+ init_command (mock_typer_ctx , "." )
168233
169234 # The steps table is a Rich Table passed to console.print.
170235 # Render it to plain text and check for "flash login".
@@ -186,12 +251,12 @@ def test_flash_login_step_displayed(self, mock_cwd, mock_context, tmp_path):
186251 assert "flash login" in buf .getvalue ()
187252
188253 def test_status_message_for_new_directory (
189- self , mock_context , tmp_path , monkeypatch
254+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
190255 ):
191256 """Test status message while creating new directory."""
192257 monkeypatch .chdir (tmp_path )
193258
194- init_command ("my_project" )
259+ init_command (mock_typer_ctx , "my_project" )
195260
196261 # Check that status was called with appropriate message
197262 mock_context ["console" ].status .assert_called_once ()
@@ -200,12 +265,12 @@ def test_status_message_for_new_directory(
200265
201266 @patch ("pathlib.Path.cwd" )
202267 def test_status_message_for_current_directory (
203- self , mock_cwd , mock_context , tmp_path
268+ self , mock_cwd , mock_typer_ctx , mock_context , tmp_path
204269 ):
205270 """Test status message while initializing current directory."""
206271 mock_cwd .return_value = tmp_path
207272
208- init_command ("." )
273+ init_command (mock_typer_ctx , "." )
209274
210275 # Check that status was called with initialization message
211276 mock_context ["console" ].status .assert_called_once ()
@@ -217,36 +282,36 @@ class TestInitCommandProjectNameHandling:
217282 """Tests for project name handling."""
218283
219284 def test_special_characters_in_project_name (
220- self , mock_context , tmp_path , monkeypatch
285+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
221286 ):
222287 """Test project name with special characters."""
223288 monkeypatch .chdir (tmp_path )
224289
225- init_command ("my-project_123" )
290+ init_command (mock_typer_ctx , "my-project_123" )
226291
227292 # Verify directory was created with the exact name
228293 assert (tmp_path / "my-project_123" ).exists ()
229294
230295 def test_console_called_with_panels_and_tables (
231- self , mock_context , tmp_path , monkeypatch
296+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
232297 ):
233298 """Test that console prints panels and tables."""
234299 monkeypatch .chdir (tmp_path )
235300
236- init_command ("test_project" )
301+ init_command (mock_typer_ctx , "test_project" )
237302
238303 # Verify console.print was called multiple times
239304 assert (
240305 mock_context ["console" ].print .call_count >= 4
241306 ) # Panel, "Next steps:", Table, API key info
242307
243308 def test_directory_created_matches_argument (
244- self , mock_context , tmp_path , monkeypatch
309+ self , mock_typer_ctx , mock_context , tmp_path , monkeypatch
245310 ):
246311 """Test that directory created matches the argument."""
247312 monkeypatch .chdir (tmp_path )
248313
249- init_command ("my_awesome_project" )
314+ init_command (mock_typer_ctx , "my_awesome_project" )
250315
251316 # Verify directory was created with exact name
252317 assert (tmp_path / "my_awesome_project" ).exists ()
0 commit comments