diff --git a/issue_metrics.py b/issue_metrics.py old mode 100644 new mode 100755 diff --git a/test_time_in_draft.py b/test_time_in_draft.py index 8a4b950..ff253d5 100644 --- a/test_time_in_draft.py +++ b/test_time_in_draft.py @@ -28,7 +28,7 @@ def test_time_in_draft_with_ready_for_review(self): """ self.issue.issue.events.return_value = [ MagicMock( - event="converted_to_draft", + event="convert_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), ), MagicMock( @@ -46,7 +46,7 @@ def test_time_in_draft_without_ready_for_review(self): """ self.issue.issue.events.return_value = [ MagicMock( - event="converted_to_draft", + event="convert_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), ), ] @@ -63,7 +63,7 @@ def test_time_in_draft_multiple_intervals(self): """ self.issue.issue.events.return_value = [ MagicMock( - event="converted_to_draft", + event="convert_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), ), MagicMock( @@ -71,7 +71,7 @@ def test_time_in_draft_multiple_intervals(self): created_at=datetime(2021, 1, 3, tzinfo=pytz.utc), ), MagicMock( - event="converted_to_draft", + event="convert_to_draft", created_at=datetime(2021, 1, 5, tzinfo=pytz.utc), ), MagicMock( @@ -89,7 +89,7 @@ def test_time_in_draft_ongoing_draft(self): """ self.issue.issue.events.return_value = [ MagicMock( - event="converted_to_draft", + event="convert_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), ), ] @@ -117,7 +117,7 @@ def test_time_in_draft_without_ready_for_review_and_closed(self): """ self.issue.issue.events.return_value = [ MagicMock( - event="converted_to_draft", + event="convert_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), ), ] @@ -133,9 +133,9 @@ def test_time_in_draft_initially_created_as_draft(self): Test measure_time_in_draft with a PR initially created as draft. """ # Set up issue created_at time - self.issue.issue.created_at = "2021-01-01T00:00:00Z" + self.issue.issue.created_at = datetime(2021, 1, 1, tzinfo=pytz.utc) - # Mock events with only ready_for_review (no converted_to_draft) + # Mock events with only ready_for_review (no convert_to_draft) self.issue.issue.events.return_value = [ MagicMock( event="ready_for_review", @@ -159,7 +159,7 @@ def test_time_in_draft_initially_created_as_draft_still_open(self): Test measure_time_in_draft with a PR initially created as draft and still in draft. """ # Set up issue created_at time - self.issue.issue.created_at = "2021-01-01T00:00:00Z" + self.issue.issue.created_at = datetime(2021, 1, 1, tzinfo=pytz.utc) # Mock events with no ready_for_review events (still draft) self.issue.issue.events.return_value = [] @@ -192,7 +192,7 @@ def test_time_in_draft_with_attribute_error_scenario(self): issue_search_result.issue.state = "open" issue_search_result.issue.events.return_value = [ MagicMock( - event="converted_to_draft", + event="convert_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), ), ] @@ -204,6 +204,40 @@ def test_time_in_draft_with_attribute_error_scenario(self): expected = timedelta(days=3) self.assertEqual(result, expected, "The time in draft should be 3 days.") + def test_time_in_draft_with_iterator_events(self): + """ + Test measure_time_in_draft with events() returning an iterator instead of a list. + This test ensures the function works correctly when events() returns an iterator + (as it does in the real GitHub API), which can only be consumed once. + """ + # Set up issue created_at time + self.issue.issue.created_at = datetime(2021, 1, 1, tzinfo=pytz.utc) + + # Create an iterator of events (simulating real GitHub API behavior) + def events_iterator(): + return iter( + [ + MagicMock( + event="convert_to_draft", + created_at=datetime(2021, 1, 1, tzinfo=pytz.utc), + ), + MagicMock( + event="ready_for_review", + created_at=datetime(2021, 1, 3, tzinfo=pytz.utc), + ), + ] + ) + + self.issue.issue.events = events_iterator + + result = measure_time_in_draft(self.issue) + expected = timedelta(days=2) + self.assertEqual( + result, + expected, + "The time in draft should be 2 days when events() returns an iterator.", + ) + class TestGetStatsTimeInDraft(unittest.TestCase): """ diff --git a/time_in_draft.py b/time_in_draft.py index a663cc5..27ebc0c 100644 --- a/time_in_draft.py +++ b/time_in_draft.py @@ -24,7 +24,7 @@ def measure_time_in_draft( returns: Union[timedelta, None]: Total time the pull request has spent in draft state. """ - events = issue.issue.events() + events = list(issue.issue.events()) draft_start = None total_draft_time = timedelta(0) @@ -35,25 +35,23 @@ def measure_time_in_draft( if pull_request is None: pull_request = issue.issue.pull_request() - pr_created_at = datetime.fromisoformat( - issue.issue.created_at.replace("Z", "+00:00") - ) + pr_created_at = issue.issue.created_at # Look for ready_for_review events to determine if PR was initially draft ready_for_review_events = [] - converted_to_draft_events = [] + convert_to_draft_events = [] for event in events: if event.event == "ready_for_review": ready_for_review_events.append(event) - elif event.event == "converted_to_draft": - converted_to_draft_events.append(event) + elif event.event == "convert_to_draft": + convert_to_draft_events.append(event) # If there are ready_for_review events, check if PR was initially draft if ready_for_review_events: first_ready_event = min(ready_for_review_events, key=lambda x: x.created_at) prior_draft_events = [ e - for e in converted_to_draft_events + for e in convert_to_draft_events if e.created_at < first_ready_event.created_at ] @@ -62,7 +60,7 @@ def measure_time_in_draft( total_draft_time += first_ready_event.created_at - pr_created_at # If there are no ready_for_review events but the PR is currently draft, it might be initially draft and still open - elif not ready_for_review_events and not converted_to_draft_events: + elif not ready_for_review_events and not convert_to_draft_events: # Check if PR is currently draft and open if ( hasattr(pull_request, "draft") @@ -77,7 +75,7 @@ def measure_time_in_draft( pass for event in events: - if event.event == "converted_to_draft": + if event.event == "convert_to_draft": draft_start = event.created_at elif event.event == "ready_for_review" and draft_start: # Calculate draft time for this interval @@ -88,7 +86,12 @@ def measure_time_in_draft( if draft_start and issue.issue.state == "open": total_draft_time += datetime.now(pytz.utc) - draft_start - return total_draft_time if total_draft_time > timedelta(0) else None + # Round to the nearest second + return ( + timedelta(seconds=round(total_draft_time.total_seconds())) + if total_draft_time > timedelta(0) + else None + ) def get_stats_time_in_draft(