Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions criclive/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -96,11 +100,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 = ""
Expand Down Expand Up @@ -129,6 +129,20 @@ def _parse_match(data):
}


def _format_match_date(start_ts):
"""Format a Cricbuzz timestamp, or return "" when it is missing or invalid."""
if isinstance(start_ts, bool):
return ""
try:
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")


def _format_score(team_score):
"""Format innings scores into a readable string."""
parts = []
Expand Down
78 changes: 67 additions & 11 deletions tests.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import copy
import json
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 = {
Expand Down Expand Up @@ -72,28 +73,83 @@ 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(1700000000000), "Nov 14, 22:13 GMT")

def test_formats_numeric_string_timestamp(self):
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"""
<html><head></head><body>
<script>
self.__next_f.push([1,"matchesList\\":{{\\"matches\\":[{matches_json}]}}"])
</script>
</body></html>
"""


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"""
<html><head></head><body>
<script>
self.__next_f.push([1,"matchesList\\":{{\\"matches\\":[{match_json}]}}"])
</script>
</body></html>
"""
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

scores = get_scores()
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()
Expand Down