Skip to content

Commit 07d4718

Browse files
committed
Add tests for cycle time and first response time using ready_for_review events
1 parent c6b7f11 commit 07d4718

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed

backend/analytics_server/tests/service/code/sync/test_etl_code_analytics.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from mhq.service.code.sync.etl_code_analytics import CodeETLAnalyticsService
44
from mhq.store.models.code import PullRequestState, PullRequestEventState
5+
from mhq.store.models.code.enums import PullRequestEventType
56
from mhq.utils.time import time_now
67
from tests.factories.models.code import (
78
get_pull_request,
@@ -605,3 +606,190 @@ def test_create_pr_metrics_bot_detection_in_review_events():
605606

606607
for bot_reviewer in bot_reviewers:
607608
assert bot_reviewer not in pr_metrics.reviewers
609+
610+
611+
def test_cycle_time_uses_ready_for_review_when_available():
612+
"""Test that cycle time uses first ready_for_review event as start time when available."""
613+
pr_service = CodeETLAnalyticsService()
614+
t1 = time_now()
615+
t2 = t1 + timedelta(hours=2)
616+
t3 = t2 + timedelta(days=1)
617+
618+
pr = get_pull_request(
619+
state=PullRequestState.MERGED,
620+
created_at=t1,
621+
state_changed_at=t3,
622+
updated_at=t3
623+
)
624+
625+
ready_for_review_event = get_pull_request_event(
626+
pull_request_id=pr.id,
627+
type=PullRequestEventType.READY_FOR_REVIEW.value,
628+
created_at=t2
629+
)
630+
631+
performance = pr_service.get_pr_performance(pr, [ready_for_review_event])
632+
633+
assert performance.cycle_time == 86400
634+
635+
636+
def test_cycle_time_falls_back_to_created_at_when_no_ready_for_review():
637+
"""Test that cycle time uses PR creation time when no ready_for_review events exist."""
638+
pr_service = CodeETLAnalyticsService()
639+
t1 = time_now()
640+
t2 = t1 + timedelta(days=1)
641+
642+
pr = get_pull_request(
643+
state=PullRequestState.MERGED,
644+
created_at=t1,
645+
state_changed_at=t2,
646+
updated_at=t2
647+
)
648+
649+
performance = pr_service.get_pr_performance(pr, [])
650+
651+
assert performance.cycle_time == 86400
652+
653+
654+
def test_cycle_time_uses_earliest_ready_for_review_when_multiple():
655+
"""Test that cycle time uses the earliest ready_for_review event when multiple exist."""
656+
pr_service = CodeETLAnalyticsService()
657+
t1 = time_now()
658+
t2 = t1 + timedelta(hours=1)
659+
t3 = t1 + timedelta(hours=3)
660+
t4 = t1 + timedelta(days=1)
661+
662+
pr = get_pull_request(
663+
state=PullRequestState.MERGED,
664+
created_at=t1,
665+
state_changed_at=t4,
666+
updated_at=t4
667+
)
668+
669+
ready_for_review_1 = get_pull_request_event(
670+
pull_request_id=pr.id,
671+
type=PullRequestEventType.READY_FOR_REVIEW.value,
672+
created_at=t2
673+
)
674+
675+
ready_for_review_2 = get_pull_request_event(
676+
pull_request_id=pr.id,
677+
type=PullRequestEventType.READY_FOR_REVIEW.value,
678+
created_at=t3
679+
)
680+
681+
performance = pr_service.get_pr_performance(pr, [ready_for_review_2, ready_for_review_1])
682+
683+
assert performance.cycle_time == 82800
684+
685+
686+
def test_first_response_time_uses_ready_for_review_when_available():
687+
"""Test that first response time uses ready_for_review as start time when available."""
688+
pr_service = CodeETLAnalyticsService()
689+
t1 = time_now()
690+
t2 = t1 + timedelta(hours=2)
691+
t3 = t2 + timedelta(hours=1)
692+
693+
pr = get_pull_request(created_at=t1, updated_at=t1)
694+
695+
ready_for_review_event = get_pull_request_event(
696+
pull_request_id=pr.id,
697+
type=PullRequestEventType.READY_FOR_REVIEW.value,
698+
created_at=t2
699+
)
700+
701+
review_event = get_pull_request_event(
702+
pull_request_id=pr.id,
703+
type=PullRequestEventType.REVIEW.value,
704+
created_at=t3
705+
)
706+
707+
performance = pr_service.get_pr_performance(pr, [ready_for_review_event, review_event])
708+
assert performance.first_review_time == 3600
709+
710+
711+
def test_first_response_time_falls_back_to_created_at_when_no_ready_for_review():
712+
"""Test that first response time uses PR creation time when no ready_for_review events exist."""
713+
pr_service = CodeETLAnalyticsService()
714+
t1 = time_now()
715+
t2 = t1 + timedelta(hours=3)
716+
pr = get_pull_request(created_at=t1, updated_at=t1)
717+
718+
review_event = get_pull_request_event(
719+
pull_request_id=pr.id,
720+
type=PullRequestEventType.REVIEW.value,
721+
created_at=t2
722+
)
723+
724+
performance = pr_service.get_pr_performance(pr, [review_event])
725+
assert performance.first_review_time == 10800
726+
727+
728+
def test_cycle_time_ready_for_review_with_draft_pr_workflow():
729+
"""Test cycle time calculation for draft PR workflow with ready_for_review event."""
730+
pr_service = CodeETLAnalyticsService()
731+
t1 = time_now()
732+
t2 = t1 + timedelta(days=2) # ready for review 2 days after creation (draft period)
733+
t3 = t2 + timedelta(hours=4) # first review
734+
t4 = t2 + timedelta(days=1) # merged
735+
736+
pr = get_pull_request(
737+
state=PullRequestState.MERGED,
738+
created_at=t1,
739+
state_changed_at=t4,
740+
updated_at=t4
741+
)
742+
743+
ready_for_review_event = get_pull_request_event(
744+
pull_request_id=pr.id,
745+
type=PullRequestEventType.READY_FOR_REVIEW.value,
746+
created_at=t2
747+
)
748+
749+
review_event = get_pull_request_event(
750+
pull_request_id=pr.id,
751+
type=PullRequestEventType.REVIEW.value,
752+
created_at=t3
753+
)
754+
755+
performance = pr_service.get_pr_performance(pr, [ready_for_review_event, review_event])
756+
757+
assert performance.cycle_time == 86400
758+
assert performance.first_review_time == 14400
759+
760+
def test_cycle_time_ready_for_review_after_first_review():
761+
"""Test cycle time when ready_for_review event occurs after first review (edge case)."""
762+
pr_service = CodeETLAnalyticsService()
763+
t1 = time_now()
764+
t2 = t1 + timedelta(hours=1) # first review
765+
t3 = t1 + timedelta(hours=2) # ready for review (after review)
766+
t4 = t1 + timedelta(days=1) # merged
767+
768+
pr = get_pull_request(
769+
state=PullRequestState.MERGED,
770+
created_at=t1,
771+
state_changed_at=t4,
772+
updated_at=t4
773+
)
774+
775+
review_event = get_pull_request_event(
776+
pull_request_id=pr.id,
777+
type=PullRequestEventType.REVIEW.value,
778+
created_at=t2
779+
)
780+
781+
ready_for_review_event = get_pull_request_event(
782+
pull_request_id=pr.id,
783+
type=PullRequestEventType.READY_FOR_REVIEW.value,
784+
created_at=t3
785+
)
786+
787+
performance = pr_service.get_pr_performance(pr, [review_event, ready_for_review_event])
788+
789+
# Should still use ready_for_review as start time for cycle time
790+
# t4 - t3 = 22 hours = 79200 seconds
791+
assert performance.cycle_time == 79200
792+
# First response time should be from ready_for_review to first review
793+
# Since review happened before ready_for_review, this should be from creation time
794+
# t2 - t1 = 1 hour = 3600 seconds
795+
assert performance.first_review_time == -3600

0 commit comments

Comments
 (0)