From a2ab2674e3c438ab32a3dda339dc48f3c287e7d1 Mon Sep 17 00:00:00 2001 From: Alfredo Di Stasio Date: Mon, 27 Apr 2026 15:08:34 +0200 Subject: [PATCH] Keep invalid datetimes at end of sort --- app/services/processing.py | 29 ++++++++++++++---- tests/test_processing.py | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/app/services/processing.py b/app/services/processing.py index 60628d6..4c01875 100644 --- a/app/services/processing.py +++ b/app/services/processing.py @@ -47,7 +47,7 @@ def sort_records( reverse = options.order == "desc" if options.sort_by == "datetime": - key_func = _datetime_key + return _sort_records_by_datetime(records, reverse) elif options.sort_by == "severity": key_func = _severity_key else: @@ -56,17 +56,34 @@ def sort_records( return sorted(records, key=key_func, reverse=reverse) -def _datetime_key(record: dict[str, str]) -> tuple[int, datetime]: +def _sort_records_by_datetime( + records: Iterable[dict[str, str]], reverse: bool +) -> list[dict[str, str]]: + """Sort valid datetimes normally and always place invalid/missing values last.""" + valid_records: list[tuple[datetime, dict[str, str]]] = [] + invalid_records: list[dict[str, str]] = [] + + for record in records: + parsed_datetime = _parse_datetime(record) + if parsed_datetime is None: + invalid_records.append(record) + continue + valid_records.append((parsed_datetime, record)) + + sorted_valid_records = sorted(valid_records, key=lambda item: item[0], reverse=reverse) + return [record for _parsed, record in sorted_valid_records] + invalid_records + + +def _parse_datetime(record: dict[str, str]) -> datetime | None: date_value = record.get("v015xxxxdate", "").strip() time_value = record.get("time", "").strip() if not date_value or not time_value: - return (1, datetime.max) + return None try: - parsed = datetime.strptime(f"{date_value} {time_value}", "%Y-%m-%d %H:%M:%S") + return datetime.strptime(f"{date_value} {time_value}", "%Y-%m-%d %H:%M:%S") except ValueError: - return (1, datetime.max) - return (0, parsed) + return None def _severity_key(record: dict[str, str]) -> tuple[int, str]: diff --git a/tests/test_processing.py b/tests/test_processing.py index a307f18..5508606 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -44,3 +44,65 @@ def test_sort_records_by_severity_desc_uses_defined_ranking(): "medium", "info", ] + + +def test_sort_records_by_datetime_asc_places_invalid_records_last(): + records = [ + {"v015xxxxdate": "2024-05-03", "time": "08:00:00", "msg": "latest-valid"}, + {"v015xxxxdate": "", "time": "09:00:00", "msg": "missing-date"}, + {"v015xxxxdate": "2024-05-01", "time": "10:00:00", "msg": "earliest-valid"}, + {"v015xxxxdate": "2024-05-02", "time": "", "msg": "missing-time"}, + {"v015xxxxdate": "bad-date", "time": "99:99:99", "msg": "invalid-datetime"}, + {"v015xxxxdate": "2024-05-02", "time": "09:30:00", "msg": "middle-valid"}, + ] + options = ProcessingOptions( + policy_cs="", + policy_ci="", + severity_cs="", + severity_ci="", + sort_by="datetime", + order="asc", + mode="vendor", + ) + + sorted_records = sort_records(records, options) + + assert [record["msg"] for record in sorted_records] == [ + "earliest-valid", + "middle-valid", + "latest-valid", + "missing-date", + "missing-time", + "invalid-datetime", + ] + + +def test_sort_records_by_datetime_desc_places_invalid_records_last(): + records = [ + {"v015xxxxdate": "2024-05-03", "time": "08:00:00", "msg": "latest-valid"}, + {"v015xxxxdate": "", "time": "09:00:00", "msg": "missing-date"}, + {"v015xxxxdate": "2024-05-01", "time": "10:00:00", "msg": "earliest-valid"}, + {"v015xxxxdate": "2024-05-02", "time": "", "msg": "missing-time"}, + {"v015xxxxdate": "bad-date", "time": "99:99:99", "msg": "invalid-datetime"}, + {"v015xxxxdate": "2024-05-02", "time": "09:30:00", "msg": "middle-valid"}, + ] + options = ProcessingOptions( + policy_cs="", + policy_ci="", + severity_cs="", + severity_ci="", + sort_by="datetime", + order="desc", + mode="vendor", + ) + + sorted_records = sort_records(records, options) + + assert [record["msg"] for record in sorted_records] == [ + "latest-valid", + "middle-valid", + "earliest-valid", + "missing-date", + "missing-time", + "invalid-datetime", + ]