From cade6f3dc08f1d25341efd368c0c89570627e0bc Mon Sep 17 00:00:00 2001 From: DevarshP610 Date: Tue, 20 Oct 2026 16:50:43 -0500 Subject: [PATCH 1/2] fix(parser): tolerate malformed match timestamps Keep a malformed upstream startDate from aborting the entire score refresh, while preserving valid numeric timestamps and covering the behavior with tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- criclive/main.py | 17 ++++++++++++----- tests.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/criclive/main.py b/criclive/main.py index c5b37c4..87560de 100644 --- a/criclive/main.py +++ b/criclive/main.py @@ -96,11 +96,7 @@ def _parse_match(data): status = info.get("stateTitle", "") or info.get("state", "") - start_ts = info.get("startDate") - date_str = "" - if start_ts: - dt = datetime.fromtimestamp(start_ts / 1000, tz=timezone.utc) - date_str = dt.strftime("%b %d, %H:%M GMT") + date_str = _format_match_date(info.get("startDate")) venue = info.get("venueInfo", {}) venue_str = "" @@ -129,6 +125,17 @@ def _parse_match(data): } +def _format_match_date(start_ts): + """Format a Cricbuzz timestamp without dropping the whole match on bad data.""" + if start_ts in (None, ""): + return "" + try: + dt = datetime.fromtimestamp(float(start_ts) / 1000, tz=timezone.utc) + except (OverflowError, TypeError, ValueError): + return "" + return dt.strftime("%b %d, %H:%M GMT") + + def _format_score(team_score): """Format innings scores into a readable string.""" parts = [] diff --git a/tests.py b/tests.py index 7e7b56c..e5c907f 100644 --- a/tests.py +++ b/tests.py @@ -2,7 +2,7 @@ import unittest from unittest.mock import patch, MagicMock -from criclive.main import get_scores, _format_score, _parse_match +from criclive.main import get_scores, _format_score, _format_match_date, _parse_match SAMPLE_MATCH = { @@ -72,6 +72,33 @@ def test_parse_match_no_teams(self): data = {"match": {"matchInfo": {}, "matchScore": {}}} self.assertIsNone(_parse_match(data)) + def test_parse_match_ignores_malformed_start_date(self): + data = { + "match": { + "matchInfo": { + "matchId": 12345, + "team1": {"teamName": "India", "teamSName": "IND"}, + "team2": {"teamName": "Australia", "teamSName": "AUS"}, + "startDate": "not-a-timestamp", + }, + "matchScore": {}, + } + } + result = _parse_match(data) + self.assertIsNotNone(result) + self.assertEqual(result["date"], "") + + +class TestFormatMatchDate(unittest.TestCase): + def test_formats_numeric_timestamp(self): + self.assertEqual(_format_match_date(0), "Jan 01, 00:00 GMT") + + def test_formats_numeric_string_timestamp(self): + self.assertEqual(_format_match_date("0"), "Jan 01, 00:00 GMT") + + def test_invalid_timestamp_returns_empty(self): + self.assertEqual(_format_match_date("invalid"), "") + class TestGetScores(unittest.TestCase): @patch("criclive.main.requests.get") From d23296cac2f595d0a6fda36454206cfc22b5de01 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 5 Nov 2026 09:24:56 +0000 Subject: [PATCH 2/2] fix(parser): skip malformed matches and reject bad timestamps Catch OSError for out-of-range timestamps, treat 0 and booleans as missing dates, and skip a single malformed match instead of dropping every match. --- criclive/main.py | 17 +++++++++++----- tests.py | 53 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/criclive/main.py b/criclive/main.py index 87560de..a0522e1 100644 --- a/criclive/main.py +++ b/criclive/main.py @@ -45,7 +45,11 @@ def _parse_matches_from_script(text): start = m.start() - 1 # include the opening { match_data = _extract_json_object(unescaped, start) if match_data: - parsed = _parse_match(match_data) + try: + parsed = _parse_match(match_data) + except (AttributeError, TypeError, ValueError): + # Skip one malformed match instead of losing every match + continue if parsed: matches.append(parsed) @@ -126,12 +130,15 @@ def _parse_match(data): def _format_match_date(start_ts): - """Format a Cricbuzz timestamp without dropping the whole match on bad data.""" - if start_ts in (None, ""): + """Format a Cricbuzz timestamp, or return "" when it is missing or invalid.""" + if isinstance(start_ts, bool): return "" try: - dt = datetime.fromtimestamp(float(start_ts) / 1000, tz=timezone.utc) - except (OverflowError, TypeError, ValueError): + start_ms = float(start_ts) + if not start_ms: + return "" + dt = datetime.fromtimestamp(start_ms / 1000, tz=timezone.utc) + except (OverflowError, OSError, TypeError, ValueError): return "" return dt.strftime("%b %d, %H:%M GMT") diff --git a/tests.py b/tests.py index e5c907f..abbe4cc 100644 --- a/tests.py +++ b/tests.py @@ -1,3 +1,4 @@ +import copy import json import unittest from unittest.mock import patch, MagicMock @@ -91,29 +92,44 @@ def test_parse_match_ignores_malformed_start_date(self): class TestFormatMatchDate(unittest.TestCase): def test_formats_numeric_timestamp(self): - self.assertEqual(_format_match_date(0), "Jan 01, 00:00 GMT") + self.assertEqual(_format_match_date(1700000000000), "Nov 14, 22:13 GMT") def test_formats_numeric_string_timestamp(self): - self.assertEqual(_format_match_date("0"), "Jan 01, 00:00 GMT") + self.assertEqual(_format_match_date("1700000000000"), "Nov 14, 22:13 GMT") + + def test_zero_timestamp_returns_empty(self): + self.assertEqual(_format_match_date(0), "") + self.assertEqual(_format_match_date("0"), "") + + def test_out_of_range_timestamp_returns_empty(self): + self.assertEqual(_format_match_date(1e20), "") + + def test_boolean_timestamp_returns_empty(self): + self.assertEqual(_format_match_date(True), "") def test_invalid_timestamp_returns_empty(self): self.assertEqual(_format_match_date("invalid"), "") +def _live_scores_page(*matches): + """Build a minimal HTML page with embedded match data (compact JSON, no spaces).""" + matches_json = ",".join( + json.dumps(m, separators=(",", ":")).replace('"', '\\"') for m in matches + ) + return f""" + + + + """ + + class TestGetScores(unittest.TestCase): @patch("criclive.main.requests.get") def test_get_scores_parses_embedded_json(self, mock_get): - # Build a minimal HTML page with embedded match data (compact JSON, no spaces) - match_json = json.dumps(SAMPLE_MATCH, separators=(",", ":")).replace('"', '\\"') - html = f""" - - - - """ mock_response = MagicMock() - mock_response.text = html + mock_response.text = _live_scores_page(SAMPLE_MATCH) mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response @@ -121,6 +137,19 @@ def test_get_scores_parses_embedded_json(self, mock_get): self.assertEqual(len(scores), 1) self.assertEqual(scores[0]["first_team"]["name"], "IND") + @patch("criclive.main.requests.get") + def test_get_scores_skips_malformed_match(self, mock_get): + bad_match = copy.deepcopy(SAMPLE_MATCH) + bad_match["match"]["matchInfo"]["matchId"] = 999 + bad_match["match"]["matchScore"]["team1Score"] = None + mock_response = MagicMock() + mock_response.text = _live_scores_page(bad_match, SAMPLE_MATCH) + mock_response.raise_for_status = MagicMock() + mock_get.return_value = mock_response + + scores = get_scores() + self.assertEqual([s["match_id"] for s in scores], [12345]) + @patch("criclive.main.requests.get") def test_get_scores_empty_page(self, mock_get): mock_response = MagicMock()