44
55from cli_output .status import (
66 _create_progress_bar ,
7+ _display_bucket_credit_line ,
78 _display_credit_line ,
8- _display_purchased_credit_line ,
99 _display_status_message ,
1010 print_status ,
1111)
@@ -132,8 +132,8 @@ def test_timezone_naive_datetime(self, mock_console):
132132 mock_console .print .assert_called_once ()
133133
134134
135- class TestDisplayPurchasedCreditLine :
136- """Tests for _display_purchased_credit_line function."""
135+ class TestDisplayBucketCreditLine :
136+ """Tests for _display_bucket_credit_line function."""
137137
138138 @patch ("cli_output.status.console" )
139139 def test_display_active_purchased_credits (self , mock_console ):
@@ -143,13 +143,28 @@ def test_display_active_purchased_credits(self, mock_console):
143143 "remaining" : 20 ,
144144 "expiry_date" : "2028-12-12T00:00:00+00:00" ,
145145 }
146- _display_purchased_credit_line (bucket )
146+ _display_bucket_credit_line (bucket , "Purchased" )
147147
148148 call_args = mock_console .print .call_args [0 ][0 ]
149149 assert "Purchased" in call_args
150150 assert "20 of 100 remaining" in call_args
151151 assert "expires Dec 12, 2028" in call_args
152152
153+ @patch ("cli_output.status.console" )
154+ def test_display_active_promo_credits (self , mock_console ):
155+ """Test displaying active promo credits."""
156+ bucket = {
157+ "total" : 100 ,
158+ "remaining" : 20 ,
159+ "expiry_date" : "2028-12-12T00:00:00+00:00" ,
160+ }
161+ _display_bucket_credit_line (bucket , "Promo" )
162+
163+ call_args = mock_console .print .call_args [0 ][0 ]
164+ assert "Promo" in call_args
165+ assert "20 of 100 remaining" in call_args
166+ assert "expires Dec 12, 2028" in call_args
167+
153168 @patch ("cli_output.status.console" )
154169 def test_display_expired_purchased_credits (self , mock_console ):
155170 """Test displaying expired purchased credits."""
@@ -158,7 +173,7 @@ def test_display_expired_purchased_credits(self, mock_console):
158173 "remaining" : 20 ,
159174 "expiry_date" : "2024-01-01T00:00:00+00:00" ,
160175 }
161- _display_purchased_credit_line (bucket )
176+ _display_bucket_credit_line (bucket , "Purchased" )
162177
163178 call_args = mock_console .print .call_args [0 ][0 ]
164179 assert "expired Jan 1, 2024" in call_args
@@ -173,7 +188,7 @@ def test_timezone_naive_datetime(self, mock_console):
173188 "expiry_date" : "2026-06-12T00:00:00" , # No timezone
174189 }
175190 # Should not raise exception
176- _display_purchased_credit_line (bucket )
191+ _display_bucket_credit_line (bucket , "Purchased" )
177192 mock_console .print .assert_called_once ()
178193
179194
@@ -187,7 +202,7 @@ def test_has_active_plan_credits(self, mock_console):
187202 "remaining" : 10 ,
188203 "period_end" : "2028-12-01T00:00:00+00:00" ,
189204 }
190- _display_status_message (plan_credits , [])
205+ _display_status_message (plan_credits , [], [] )
191206
192207 # Should not print warning message
193208 mock_console .print .assert_not_called ()
@@ -201,7 +216,21 @@ def test_has_active_purchased_credits(self, mock_console):
201216 "expiry_date" : "2030-06-12T00:00:00+00:00" ,
202217 }
203218 ]
204- _display_status_message (None , purchased_credits )
219+ _display_status_message (None , purchased_credits , [])
220+
221+ # Should not print warning message
222+ mock_console .print .assert_not_called ()
223+
224+ @patch ("cli_output.status.console" )
225+ def test_has_active_promo_credits (self , mock_console ):
226+ """Test when user only has active promo credits."""
227+ promo_credits = [
228+ {
229+ "remaining" : 20 ,
230+ "expiry_date" : "2030-06-12T00:00:00+00:00" ,
231+ }
232+ ]
233+ _display_status_message (None , [], promo_credits )
205234
206235 # Should not print warning message
207236 mock_console .print .assert_not_called ()
@@ -213,7 +242,7 @@ def test_no_credits_remaining(self, mock_console):
213242 "remaining" : 0 ,
214243 "period_end" : "2028-12-01T00:00:00+00:00" ,
215244 }
216- _display_status_message (plan_credits , [])
245+ _display_status_message (plan_credits , [], [] )
217246
218247 mock_console .print .assert_called_once ()
219248 call_args = mock_console .print .call_args [0 ][0 ]
@@ -226,16 +255,16 @@ def test_expired_plan_credits(self, mock_console):
226255 "remaining" : 10 ,
227256 "period_end" : "2024-01-01T00:00:00+00:00" ,
228257 }
229- _display_status_message (plan_credits , [])
258+ _display_status_message (plan_credits , [], [] )
230259
231260 mock_console .print .assert_called_once ()
232261 call_args = mock_console .print .call_args [0 ][0 ]
233262 assert "No rendering credits remaining" in call_args
234263
235264 @patch ("cli_output.status.console" )
236265 def test_null_plan_credits_and_empty_purchased (self , mock_console ):
237- """Test when plan_credits is None and purchased_credits is empty."""
238- _display_status_message (None , [])
266+ """Test when plan_credits is None and purchased/promo credits are empty."""
267+ _display_status_message (None , [], [] )
239268
240269 mock_console .print .assert_called_once ()
241270 call_args = mock_console .print .call_args [0 ][0 ]
@@ -384,6 +413,83 @@ def test_multiple_purchased_credit_buckets(self, mock_console, mock_api_class):
384413 purchased_calls = [c for c in calls if "Purchased" in c ]
385414 assert len (purchased_calls ) == 2
386415
416+ @patch ("cli_output.status.codeplain_api.CodeplainAPI" )
417+ @patch ("cli_output.status.console" )
418+ def test_promo_credit_buckets (self , mock_console , mock_api_class ):
419+ """Test status display includes promo credit buckets."""
420+ mock_api = Mock ()
421+ mock_api_class .return_value = mock_api
422+ mock_api .connection_check .return_value = {
423+ "client_version_valid" : True ,
424+ "min_client_version" : "0.3.0" ,
425+ }
426+ mock_api .status .return_value = {
427+ "user" : {
428+ "first_name" : "John" ,
429+ "last_name" : "Doe" ,
430+ "email" : "john@example.com" ,
431+ },
432+ "api_key_label" : "test-key" ,
433+ "organization_owner_email" : "owner@example.com" ,
434+ "plan_credits" : None ,
435+ "purchased_credits" : [
436+ {
437+ "total" : 100 ,
438+ "remaining" : 50 ,
439+ "expiry_date" : "2030-06-12T00:00:00+00:00" ,
440+ },
441+ ],
442+ "promo_credits" : [
443+ {
444+ "total" : 30 ,
445+ "remaining" : 15 ,
446+ "expiry_date" : "2030-12-31T00:00:00+00:00" ,
447+ },
448+ ],
449+ }
450+
451+ print_status ("fake-key" , "http://localhost:5000" , "0.3.0" )
452+
453+ calls = [str (call ) for call in mock_console .print .call_args_list ]
454+ promo_calls = [c for c in calls if "Promo" in c ]
455+ assert len (promo_calls ) == 1
456+ # Active credits remain, so no "no credits remaining" warning
457+ assert not any ("No rendering credits remaining" in c for c in calls )
458+
459+ @patch ("cli_output.status.codeplain_api.CodeplainAPI" )
460+ @patch ("cli_output.status.console" )
461+ def test_missing_promo_credits_key_is_backward_compatible (self , mock_console , mock_api_class ):
462+ """Test status display when API response omits promo_credits (older API)."""
463+ mock_api = Mock ()
464+ mock_api_class .return_value = mock_api
465+ mock_api .connection_check .return_value = {
466+ "client_version_valid" : True ,
467+ "min_client_version" : "0.3.0" ,
468+ }
469+ mock_api .status .return_value = {
470+ "user" : {
471+ "first_name" : "John" ,
472+ "last_name" : "Doe" ,
473+ "email" : "john@example.com" ,
474+ },
475+ "api_key_label" : "test-key" ,
476+ "organization_owner_email" : "owner@example.com" ,
477+ "plan_credits" : {
478+ "type" : "free" ,
479+ "total" : 50 ,
480+ "remaining" : 10 ,
481+ "period_end" : "2030-12-01T00:00:00+00:00" ,
482+ },
483+ "purchased_credits" : [],
484+ # promo_credits intentionally omitted
485+ }
486+
487+ # Should not raise
488+ print_status ("fake-key" , "http://localhost:5000" , "0.3.0" )
489+
490+ calls = [str (call ) for call in mock_console .print .call_args_list ]
491+ assert not any ("Promo" in c for c in calls )
492+
387493
388494class TestVersionFlag :
389495 """Tests for --version flag."""
0 commit comments