| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- """Basic unit tests for TeamExtractor and Elo analyzers.
- This module uses a simple in-memory FakeDB to validate:
- - Team aliases union behavior
- - Elo snapshot and history persistence contracts
- """
- import unittest
- from typing import Any, Dict, List
- from databank.analytics.teams import TeamExtractorAnalyzer
- from databank.analytics.elo import EloAnalyzer
- from databank.core.models import Document
- class FakeDB:
- """A minimal in-memory DB stub implementing insert_many/find for tests."""
- def __init__(self) -> None:
- self.storage: Dict[str, List[Dict[str, Any]]] = {}
- def connect(self) -> None: # for parity
- """No-op connect to match real DB interface."""
- return None
- def insert_many(self, docs: List[Document]) -> int:
- """Append documents by kind; return inserted count."""
- count = 0
- for d in docs:
- kind = d.kind
- rec = {"_id": d.id, "kind": kind, "data": d.data}
- self.storage.setdefault(kind, []).append(rec)
- count += 1
- return count
- def find(
- self,
- kind: str,
- query: Dict[str, Any] | None = None,
- _projection=None,
- _limit=None,
- ): # noqa: A002 - filter naming for parity
- """Retrieve documents by kind; supports _id $in filter."""
- data = self.storage.get(kind, [])
- if not query:
- return list(data)
- # Support _id $in
- ids = None
- if "_id" in query and isinstance(query["_id"], dict) and "$in" in query["_id"]:
- ids = set(map(str, query["_id"]["$in"]))
- if ids is not None:
- return [d for d in data if str(d.get("_id")) in ids]
- return list(data)
- class TestAnalyticsTeamElo(unittest.TestCase):
- """Tests for TeamExtractorAnalyzer and EloAnalyzer integrations."""
- def test_team_extractor_alias_union(self) -> None:
- """Teams analyzer should union new aliases with existing ones."""
- db = FakeDB()
- # Existing team with alias
- db.insert_many(
- [
- Document(
- id="100",
- kind="teams",
- data={
- "team_id": "100",
- "name_canonical": "manchester city",
- "aliases": ["曼城"],
- },
- )
- ]
- )
- # Two matches with different alias spellings
- matches = [
- {
- "match": {
- "homeTeamId": 100,
- "homeTeamName": "Manchester City",
- "awayTeamId": 200,
- "awayTeamName": "Arsenal",
- }
- },
- {
- "match": {
- "homeTeamId": 300,
- "homeTeamName": "Chelsea",
- "awayTeamId": 100,
- "awayTeamName": "Man City",
- }
- },
- ]
- res = TeamExtractorAnalyzer().compute(matches, db=db)
- self.assertIn("upserted", res)
- # Check merged aliases contain existing + new
- teams = db.find("teams")
- # Find all records for _id 100 and pick the latest appended (upsert effect)
- mlist = [x for x in teams if str(x.get("_id")) == "100"]
- self.assertGreaterEqual(len(mlist), 1)
- mrec = mlist[-1]
- aliases = set(mrec["data"]["aliases"]) # type: ignore[index]
- self.assertTrue({"曼城", "Manchester City", "Man City"}.issubset(aliases))
- def test_elo_persist_snapshot_and_history(self) -> None:
- """Elo analyzer should persist snapshot and two history entries per match."""
- db = FakeDB()
- # Two matches: A beats B 1-0, then draw 0-0
- matches = [
- {
- "match": {
- "matchId": 1,
- "homeTeamId": "A",
- "awayTeamId": "B",
- "homeScore": 1,
- "awayScore": 0,
- "elapsedTime": "已完场",
- "matchTime": 1_700_000_000,
- }
- },
- {
- "match": {
- "matchId": 2,
- "homeTeamId": "A",
- "awayTeamId": "B",
- "homeScore": 0,
- "awayScore": 0,
- "elapsedTime": "已完场",
- "matchTime": 1_700_000_100,
- }
- },
- ]
- res = EloAnalyzer().compute(matches, db=db, persist=True)
- self.assertEqual(res["processed"], 2)
- # Snapshot should exist
- elo = db.find("elo_ratings")
- self.assertTrue(len(elo) >= 2)
- # History should have 4 entries (2 matches x 2 teams)
- hist = db.find("ratings_history")
- self.assertEqual(len(hist), 4)
- # Ensure history contains match_id and ts
- for h in hist:
- self.assertIn("match_id", h["data"]) # type: ignore[index]
- self.assertIn("ts", h["data"]) # type: ignore[index]
- if __name__ == "__main__":
- unittest.main()
|