From e88be5a62e2baf1ec2a72a558e79b265f9637936 Mon Sep 17 00:00:00 2001 From: saichethana28 Date: Sat, 7 Feb 2026 18:15:55 +0000 Subject: [PATCH 1/2] update project health metrics to correctly calculate latest months and add unit tests --- .../owasp/models/project_health_metrics.py | 13 ++-- .../models/project_health_metrics_test.py | 70 ++++++++++++++++++- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/backend/apps/owasp/models/project_health_metrics.py b/backend/apps/owasp/models/project_health_metrics.py index 8e055cf04a..7f8fe84eb0 100644 --- a/backend/apps/owasp/models/project_health_metrics.py +++ b/backend/apps/owasp/models/project_health_metrics.py @@ -4,7 +4,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models -from django.db.models.functions import Coalesce, ExtractMonth, TruncDate +from django.db.models.functions import Coalesce, TruncDate, TruncMonth from django.utils import timezone from apps.common.models import BulkSaveModel, TimestampedModel @@ -210,21 +210,18 @@ def get_stats() -> ProjectHealthStatsNode: ) total = stats["projects_count_total"] or 1 # Avoid division by zero monthly_overall_metrics = ( - ProjectHealthMetrics.objects.annotate(month=ExtractMonth("nest_created_at")) - .filter( - nest_created_at__gte=timezone.now() - timezone.timedelta(days=365) - ) # Last year data - .order_by("month") + ProjectHealthMetrics.objects.annotate(month=TruncMonth("nest_created_at")) + .filter(nest_created_at__gte=timezone.now() - timezone.timedelta(days=365)) .values("month") - .distinct() .annotate( score=models.Avg("score"), ) + .order_by("month") ) months = [] scores = [] for entry in monthly_overall_metrics: - months.append(entry["month"]) + months.append(entry["month"].strftime("%b %Y")) scores.append(entry["score"]) return ProjectHealthStatsNode( diff --git a/backend/tests/apps/owasp/models/project_health_metrics_test.py b/backend/tests/apps/owasp/models/project_health_metrics_test.py index 16a83481bc..5971298944 100644 --- a/backend/tests/apps/owasp/models/project_health_metrics_test.py +++ b/backend/tests/apps/owasp/models/project_health_metrics_test.py @@ -114,5 +114,73 @@ def test_handle_days_calculation(self, field_name, expected_days): metrics.last_released_at = self.FIXED_DATE metrics.owasp_page_last_updated_at = self.FIXED_DATE metrics.pull_request_last_created_at = self.FIXED_DATE - assert getattr(metrics, field_name) == expected_days + + def test_get_stats_monthly_grouping_logic(self, mocker): + """Should correctly format month and year strings from metrics.""" + from django.db.models import QuerySet + + mock_entry_1 = {"month": timezone.datetime(2024, 1, 1), "score": 80.0} + mock_entry_2 = {"month": timezone.datetime(2025, 1, 1), "score": 90.0} + + mock_query = mocker.Mock(spec=QuerySet) + mock_query.aggregate.return_value = { + "projects_count_healthy": 1, + "projects_count_need_attention": 0, + "projects_count_unhealthy": 0, + "projects_count_total": 1, + "average_score": 85.0, + "total_contributors": 10, + "total_forks": 5, + "total_stars": 20, + } + + chain = mock_query.annotate.return_value.filter.return_value.values.return_value + chain.annotate.return_value.order_by.return_value = [mock_entry_1, mock_entry_2] + + mocker.patch.object( + ProjectHealthMetrics, "get_latest_health_metrics", return_value=mock_query + ) + mocker.patch.object( + ProjectHealthMetrics.objects, "annotate", return_value=mock_query.annotate.return_value + ) + + stats = ProjectHealthMetrics.get_stats() + + assert stats.monthly_overall_scores_months == ["Jan 2024", "Jan 2025"] + assert stats.monthly_overall_scores == [80.0, 90.0] + + def test_get_stats_empty_state(self, mocker): + """Should return empty lists when no metrics exist.""" + mock_query = mocker.Mock() + mock_query.aggregate.return_value = { + "projects_count_healthy": 0, + "projects_count_need_attention": 0, + "projects_count_unhealthy": 0, + "projects_count_total": 0, + "average_score": 0.0, + "total_contributors": 0, + "total_forks": 0, + "total_stars": 0, + } + + chain = mock_query.annotate.return_value.filter.return_value.values.return_value + chain.annotate.return_value.order_by.return_value = [] + + mocker.patch.object( + ProjectHealthMetrics, "get_latest_health_metrics", return_value=mock_query + ) + mocker.patch.object( + ProjectHealthMetrics.objects, "annotate", return_value=mock_query.annotate.return_value + ) + + stats = ProjectHealthMetrics.get_stats() + + assert stats.monthly_overall_scores == [] + assert stats.monthly_overall_scores_months == [] + + @pytest.fixture + def mock_now(self, mocker): + fixed_now = timezone.make_aware(timezone.datetime(2026, 2, 7)) + mocker.patch("django.utils.timezone.now", return_value=fixed_now) + return fixed_now From 884470134e45d53eeb205c34a8d9e2944372b4db Mon Sep 17 00:00:00 2001 From: sai chethana Date: Sun, 8 Feb 2026 00:11:49 +0530 Subject: [PATCH 2/2] Update backend/tests/apps/owasp/models/project_health_metrics_test.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../tests/apps/owasp/models/project_health_metrics_test.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backend/tests/apps/owasp/models/project_health_metrics_test.py b/backend/tests/apps/owasp/models/project_health_metrics_test.py index 5971298944..cc94e1b57e 100644 --- a/backend/tests/apps/owasp/models/project_health_metrics_test.py +++ b/backend/tests/apps/owasp/models/project_health_metrics_test.py @@ -179,8 +179,3 @@ def test_get_stats_empty_state(self, mocker): assert stats.monthly_overall_scores == [] assert stats.monthly_overall_scores_months == [] - @pytest.fixture - def mock_now(self, mocker): - fixed_now = timezone.make_aware(timezone.datetime(2026, 2, 7)) - mocker.patch("django.utils.timezone.now", return_value=fixed_now) - return fixed_now