|
| 1 | +from datetime import datetime, timedelta, timezone |
| 2 | + |
| 3 | +import click |
| 4 | +import pytest |
| 5 | + |
| 6 | +from jumpstarter_cli.common import DATETIME, DURATION, DateTimeParamType, DurationParamType |
| 7 | + |
| 8 | + |
| 9 | +class TestDateTimeParamType: |
| 10 | + """Test DateTimeParamType parameter parsing and normalization.""" |
| 11 | + |
| 12 | + def test_parse_iso8601_with_timezone(self): |
| 13 | + """Test parsing ISO 8601 datetime with timezone.""" |
| 14 | + dt = DATETIME.convert("2024-01-01T12:00:00Z", None, None) |
| 15 | + assert dt.year == 2024 |
| 16 | + assert dt.month == 1 |
| 17 | + assert dt.day == 1 |
| 18 | + assert dt.hour == 12 |
| 19 | + assert dt.minute == 0 |
| 20 | + assert dt.second == 0 |
| 21 | + assert dt.tzinfo is not None |
| 22 | + assert dt.tzinfo == timezone.utc |
| 23 | + |
| 24 | + def test_parse_iso8601_naive_gets_normalized(self): |
| 25 | + """Test that naive datetime gets normalized to local timezone.""" |
| 26 | + dt = DATETIME.convert("2024-01-01T12:00:00", None, None) |
| 27 | + assert dt.year == 2024 |
| 28 | + assert dt.month == 1 |
| 29 | + assert dt.day == 1 |
| 30 | + assert dt.hour == 12 |
| 31 | + assert dt.minute == 0 |
| 32 | + assert dt.second == 0 |
| 33 | + # Should have been normalized to local timezone |
| 34 | + assert dt.tzinfo is not None |
| 35 | + |
| 36 | + def test_pass_through_datetime_object_with_timezone(self): |
| 37 | + """Test that datetime object with timezone passes through.""" |
| 38 | + input_dt = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc) |
| 39 | + dt = DATETIME.convert(input_dt, None, None) |
| 40 | + assert dt == input_dt |
| 41 | + assert dt.tzinfo == timezone.utc |
| 42 | + |
| 43 | + def test_pass_through_datetime_object_naive_gets_normalized(self): |
| 44 | + """Test that naive datetime object gets normalized.""" |
| 45 | + input_dt = datetime(2024, 1, 1, 12, 0, 0) # Naive |
| 46 | + dt = DATETIME.convert(input_dt, None, None) |
| 47 | + assert dt.year == 2024 |
| 48 | + assert dt.month == 1 |
| 49 | + assert dt.day == 1 |
| 50 | + assert dt.hour == 12 |
| 51 | + # Should have been normalized to local timezone |
| 52 | + assert dt.tzinfo is not None |
| 53 | + |
| 54 | + def test_invalid_datetime_raises_click_exception(self): |
| 55 | + """Test that invalid datetime string raises click exception.""" |
| 56 | + param_type = DateTimeParamType() |
| 57 | + with pytest.raises(click.BadParameter, match="is not a valid datetime"): |
| 58 | + param_type.convert("not-a-datetime", None, None) |
| 59 | + |
| 60 | + |
| 61 | +class TestDurationParamType: |
| 62 | + """Test DurationParamType parameter parsing.""" |
| 63 | + |
| 64 | + def test_parse_iso8601_duration(self): |
| 65 | + """Test parsing ISO 8601 duration.""" |
| 66 | + td = DURATION.convert("PT1H30M", None, None) |
| 67 | + assert td == timedelta(hours=1, minutes=30) |
| 68 | + |
| 69 | + def test_parse_time_format(self): |
| 70 | + """Test parsing HH:MM:SS format.""" |
| 71 | + td = DURATION.convert("01:30:00", None, None) |
| 72 | + assert td == timedelta(hours=1, minutes=30) |
| 73 | + |
| 74 | + def test_parse_days_and_time(self): |
| 75 | + """Test parsing 'D days, HH:MM:SS' format.""" |
| 76 | + td = DURATION.convert("2 days, 01:30:00", None, None) |
| 77 | + assert td == timedelta(days=2, hours=1, minutes=30) |
| 78 | + |
| 79 | + def test_pass_through_timedelta_object(self): |
| 80 | + """Test that timedelta object passes through.""" |
| 81 | + input_td = timedelta(hours=1, minutes=30) |
| 82 | + td = DURATION.convert(input_td, None, None) |
| 83 | + assert td == input_td |
| 84 | + |
| 85 | + def test_invalid_duration_raises_click_exception(self): |
| 86 | + """Test that invalid duration string raises click exception.""" |
| 87 | + param_type = DurationParamType() |
| 88 | + with pytest.raises(click.BadParameter, match="is not a valid duration"): |
| 89 | + param_type.convert("not-a-duration", None, None) |
| 90 | + |
0 commit comments