@@ -31,13 +31,203 @@ def test_handle_login_command(self, mock_input, mock_poll, mock_device):
3131
3232 @patch ("iclaw.commands.utils.sys" )
3333 def test_handle_copy_command (self , mock_sys ):
34- # We'll patch pyperclip inside sys.modules because it's imported inside the function
3534 mock_py = MagicMock ()
3635 with patch .dict ("sys.modules" , {"pyperclip" : mock_py }):
3736 with patch ("sys.stdout" ):
3837 utils .handle_copy_command ("text" )
3938 mock_py .copy .assert_called_with ("text" )
4039
40+ # --- New tests for model.py ---
41+
42+ def test_handle_model_command_no_token (self ):
43+ with patch ("sys.stderr" ):
44+ res = model .handle_model_command (None , "gpt-4o" )
45+ self .assertEqual (res , "gpt-4o" )
46+
47+ @patch ("iclaw.commands.model.get_models" , side_effect = Exception ("fail" ))
48+ def test_handle_model_command_fetch_error (self , mock_models ):
49+ with patch ("sys.stderr" ):
50+ res = model .handle_model_command ("t" , "gpt-4o" )
51+ self .assertEqual (res , "gpt-4o" )
52+
53+ @patch ("iclaw.commands.model.get_models" )
54+ @patch ("iclaw.commands.model.input" , return_value = "" )
55+ def test_handle_model_command_empty_input (self , mock_input , mock_models ):
56+ mock_models .return_value = [{"id" : "m1" , "owned_by" : "o1" }]
57+ with patch ("sys.stdout" ):
58+ res = model .handle_model_command ("t" , "curr" )
59+ self .assertEqual (res , "curr" )
60+
61+ @patch ("iclaw.commands.model.get_models" )
62+ @patch ("iclaw.commands.model.input" , return_value = "m1" )
63+ def test_handle_model_command_by_name (self , mock_input , mock_models ):
64+ mock_models .return_value = [{"id" : "m1" , "owned_by" : "o1" }]
65+ with patch ("sys.stdout" ):
66+ res = model .handle_model_command ("t" , "curr" )
67+ self .assertEqual (res , "m1" )
68+
69+ @patch ("iclaw.commands.model.get_models" )
70+ @patch ("iclaw.commands.model.input" , return_value = "999" )
71+ def test_handle_model_command_invalid_number (self , mock_input , mock_models ):
72+ mock_models .return_value = [{"id" : "m1" , "owned_by" : "o1" }]
73+ with patch ("sys.stdout" ):
74+ res = model .handle_model_command ("t" , "curr" )
75+ self .assertEqual (res , "curr" )
76+
77+ @patch ("iclaw.commands.model.get_models" )
78+ @patch ("iclaw.commands.model.input" , return_value = "nonexistent" )
79+ def test_handle_model_command_unknown_name (self , mock_input , mock_models ):
80+ mock_models .return_value = [{"id" : "m1" , "owned_by" : "o1" }]
81+ with patch ("sys.stdout" ):
82+ res = model .handle_model_command ("t" , "curr" )
83+ self .assertEqual (res , "curr" )
84+
85+ @patch ("iclaw.commands.model.get_models" )
86+ @patch ("iclaw.commands.model.input" , side_effect = EOFError )
87+ def test_handle_model_command_eof (self , mock_input , mock_models ):
88+ mock_models .return_value = [{"id" : "m1" , "owned_by" : "o1" }]
89+ with patch ("sys.stdout" ):
90+ res = model .handle_model_command ("t" , "curr" )
91+ self .assertEqual (res , "curr" )
92+
93+ # --- New tests for model_provider ---
94+
95+ @patch ("iclaw.commands.model.input" , return_value = "" )
96+ def test_model_provider_empty_input (self , mock_input ):
97+ with patch ("sys.stdout" ):
98+ p , t = model .handle_model_provider_command (MagicMock (), "copilot" )
99+ self .assertEqual (p , "copilot" )
100+ self .assertIsNone (t )
101+
102+ @patch ("iclaw.commands.model.handle_login_command" , return_value = "gh_tok" )
103+ @patch ("iclaw.commands.model.get_copilot_token" , return_value = "cp_tok" )
104+ @patch ("iclaw.commands.model.input" , return_value = "1" )
105+ def test_model_provider_copilot_success (self , mock_input , mock_cp , mock_login ):
106+ with patch ("sys.stdout" ):
107+ p , t = model .handle_model_provider_command (MagicMock (), "copilot" )
108+ self .assertEqual (p , "copilot" )
109+ self .assertEqual (t , "cp_tok" )
110+
111+ @patch ("iclaw.commands.model.handle_login_command" , return_value = "gh_tok" )
112+ @patch ("iclaw.commands.model.get_copilot_token" , side_effect = Exception ("err" ))
113+ @patch ("iclaw.commands.model.input" , return_value = "1" )
114+ def test_model_provider_copilot_error (self , mock_input , mock_cp , mock_login ):
115+ with patch ("sys.stdout" ), patch ("sys.stderr" ):
116+ p , t = model .handle_model_provider_command (MagicMock (), "copilot" )
117+ self .assertEqual (p , "copilot" )
118+ self .assertIsNone (t )
119+
120+ @patch ("iclaw.commands.model.handle_login_command" , return_value = None )
121+ @patch ("iclaw.commands.model.input" , return_value = "1" )
122+ def test_model_provider_copilot_no_github_token (self , mock_input , mock_login ):
123+ with patch ("sys.stdout" ):
124+ p , t = model .handle_model_provider_command (MagicMock (), "copilot" )
125+ self .assertEqual (p , "copilot" )
126+ self .assertIsNone (t )
127+
128+ @patch ("iclaw.commands.model.input" , return_value = "2" )
129+ def test_model_provider_others (self , mock_input ):
130+ with patch ("sys.stdout" ):
131+ p , t = model .handle_model_provider_command (MagicMock (), "copilot" )
132+ self .assertEqual (p , "copilot" )
133+ self .assertIsNone (t )
134+
135+ @patch ("iclaw.commands.model.input" , return_value = "99" )
136+ def test_model_provider_invalid (self , mock_input ):
137+ with patch ("sys.stdout" ):
138+ p , t = model .handle_model_provider_command (MagicMock (), "copilot" )
139+ self .assertEqual (p , "copilot" )
140+ self .assertIsNone (t )
141+
142+ # --- New tests for search_provider ---
143+
144+ @patch ("iclaw.commands.search_provider.input" , return_value = "2" )
145+ def test_search_provider_select_startpage (self , mock_input ):
146+ with patch ("sys.stdout" ):
147+ res = search_provider .handle_search_provider_command ("duckduckgo" )
148+ self .assertEqual (res , "startpage" )
149+
150+ @patch ("iclaw.commands.search_provider.input" , return_value = "99" )
151+ def test_search_provider_invalid_number (self , mock_input ):
152+ with patch ("sys.stdout" ):
153+ res = search_provider .handle_search_provider_command ("duckduckgo" )
154+ self .assertEqual (res , "duckduckgo" )
155+
156+ @patch ("iclaw.commands.search_provider.input" , return_value = "abc" )
157+ def test_search_provider_not_a_number (self , mock_input ):
158+ with patch ("sys.stdout" ):
159+ res = search_provider .handle_search_provider_command ("duckduckgo" )
160+ self .assertEqual (res , "duckduckgo" )
161+
162+ @patch ("iclaw.commands.search_provider.input" , return_value = "" )
163+ def test_search_provider_empty (self , mock_input ):
164+ with patch ("sys.stdout" ):
165+ res = search_provider .handle_search_provider_command ("duckduckgo" )
166+ self .assertEqual (res , "duckduckgo" )
167+
168+ @patch ("iclaw.commands.search_provider.os.getenv" , return_value = None )
169+ @patch ("iclaw.commands.search_provider.input" , side_effect = ["4" , "mykey" ])
170+ def test_search_provider_tavily_with_key (self , mock_input , mock_getenv ):
171+ with patch ("sys.stdout" ):
172+ res = search_provider .handle_search_provider_command ("duckduckgo" )
173+ self .assertEqual (res , "tavily" )
174+
175+ @patch ("iclaw.commands.search_provider.os.getenv" , return_value = None )
176+ @patch ("iclaw.commands.search_provider.input" , side_effect = ["4" , "" ])
177+ def test_search_provider_tavily_no_key (self , mock_input , mock_getenv ):
178+ with patch ("sys.stdout" ):
179+ res = search_provider .handle_search_provider_command ("duckduckgo" )
180+ self .assertEqual (res , "duckduckgo" )
181+
182+ @patch ("iclaw.commands.search_provider.input" , side_effect = EOFError )
183+ def test_search_provider_eof (self , mock_input ):
184+ with patch ("sys.stdout" ):
185+ res = search_provider .handle_search_provider_command ("duckduckgo" )
186+ self .assertEqual (res , "duckduckgo" )
187+
188+ # --- New tests for auth ---
189+
190+ @patch ("iclaw.commands.auth.input" , return_value = "2" )
191+ def test_login_direct_token (self , mock_input ):
192+ mock_input .side_effect = ["2" , "my_token" ]
193+ mock_path = MagicMock ()
194+ with patch ("sys.stdout" ):
195+ res = auth .handle_login_command (mock_path )
196+ self .assertEqual (res , "my_token" )
197+
198+ @patch ("iclaw.commands.auth.input" , side_effect = ["2" , "" ])
199+ def test_login_direct_token_empty (self , mock_input ):
200+ mock_path = MagicMock ()
201+ with patch ("sys.stdout" ):
202+ res = auth .handle_login_command (mock_path )
203+ self .assertIsNone (res )
204+
205+ @patch ("iclaw.commands.auth.input" , return_value = "3" )
206+ def test_login_invalid_choice (self , mock_input ):
207+ mock_path = MagicMock ()
208+ with patch ("sys.stdout" ):
209+ res = auth .handle_login_command (mock_path )
210+ self .assertIsNone (res )
211+
212+ @patch ("iclaw.commands.auth.get_device_code" , side_effect = Exception ("net err" ))
213+ @patch ("iclaw.commands.auth.input" , return_value = "1" )
214+ def test_login_device_flow_error (self , mock_input , mock_device ):
215+ mock_path = MagicMock ()
216+ with patch ("sys.stdout" ), patch ("sys.stderr" ):
217+ res = auth .handle_login_command (mock_path )
218+ self .assertIsNone (res )
219+
220+ # --- New tests for utils ---
221+
222+ def test_copy_nothing (self ):
223+ with patch ("sys.stdout" ):
224+ utils .handle_copy_command (None )
225+
226+ def test_copy_exception (self ):
227+ with patch .dict ("sys.modules" , {"pyperclip" : None }):
228+ with patch ("sys.stderr" ), patch ("sys.stdout" ):
229+ utils .handle_copy_command ("text" )
230+
41231
42232if __name__ == "__main__" :
43233 unittest .main ()
0 commit comments