- Removed direct Player model imports from service methods - Added InMemoryQueryResult for mock-compatible filtering/sorting - Added RealPlayerRepository for real DB operations - Service now accepts AbstractPlayerRepository via constructor - Filtering and sorting work with both mocks and real DB - Tests can inject MockPlayerRepository for full test coverage This enables true unit testing without database dependencies.
186 lines
5.5 KiB
Python
186 lines
5.5 KiB
Python
"""
|
|
Data Consistency Validator
|
|
Compares refactored service layer output with expected router output.
|
|
"""
|
|
|
|
# ============================================================================
|
|
# DATA STRUCTURE COMPARISON
|
|
# ============================================================================
|
|
|
|
"""
|
|
EXPECTED OUTPUT STRUCTURES (from router definition):
|
|
===================================================
|
|
|
|
GET /api/v3/players
|
|
------------------
|
|
Response: {
|
|
"count": int,
|
|
"players": [
|
|
{
|
|
"id": int,
|
|
"name": str,
|
|
"wara": float,
|
|
"image": str,
|
|
"image2": str | None,
|
|
"team_id": int,
|
|
"season": int,
|
|
"pitcher_injury": str | None,
|
|
"pos_1": str,
|
|
"pos_2": str | None,
|
|
...
|
|
"team": { "id": int, "abbrev": str, "sname": str, ... } | int # if short_output
|
|
}
|
|
]
|
|
}
|
|
|
|
GET /api/v3/players/{player_id}
|
|
-------------------------------
|
|
Response: Player dict or null
|
|
|
|
GET /api/v3/players/search
|
|
--------------------------
|
|
Response: {
|
|
"count": int,
|
|
"total_matches": int,
|
|
"all_seasons": bool,
|
|
"players": [Player dicts]
|
|
}
|
|
|
|
|
|
GET /api/v3/teams
|
|
------------------
|
|
Response: {
|
|
"count": int,
|
|
"teams": [
|
|
{
|
|
"id": int,
|
|
"abbrev": str,
|
|
"sname": str,
|
|
"lname": str,
|
|
"gmid": int | None,
|
|
"gmid2": int | None,
|
|
"manager1_id": int | None,
|
|
"manager2_id": int | None,
|
|
"division_id": int | None,
|
|
"stadium": str | None,
|
|
"thumbnail": str | None,
|
|
"color": str | None,
|
|
"dice_color": str | None,
|
|
"season": int
|
|
}
|
|
]
|
|
}
|
|
|
|
GET /api/v3/teams/{team_id}
|
|
----------------------------
|
|
Response: Team dict or null
|
|
|
|
|
|
EXPECTED BEHAVIOR DIFFERENCES (Issues Found):
|
|
=============================================
|
|
|
|
1. STATIC VS INSTANCE METHOD MISMATCH
|
|
├─ PlayerService.get_players() - Called as static in router
|
|
│ └─ ISSUE: Method has `self` parameter - will fail!
|
|
└─ TeamService.get_teams() - Correctly uses @classmethod
|
|
└─ OK: Uses cls instead of self
|
|
|
|
2. FILTER FIELD INCONSISTENCY
|
|
├─ Router: name=str (exact match filter)
|
|
└─ Service: name.lower() comparison
|
|
└─ ISSUE: Different behavior!
|
|
|
|
3. POSITION FILTER INCOMPLETE
|
|
├─ Router: pos=[list of positions]
|
|
└─ Service: Only checks pos_1 through pos_8
|
|
└─ OK: Actually correct implementation
|
|
|
|
4. CSV OUTPUT DIFFERENCE
|
|
├─ Router: csv=bool, returns Response with content
|
|
└─ Service: as_csv=bool, returns CSV string
|
|
└─ OK: Just parameter name difference
|
|
|
|
5. INJURED FILTER SEMANTICS
|
|
├─ Router: is_injured=True → show injured players
|
|
└─ Service: is_injured is not None → filter il_return IS NOT NULL
|
|
└─ OK: Same behavior
|
|
|
|
6. SORT PARAMETER MAPPING
|
|
├─ Router: sort="name-asc" | "cost-desc" | etc
|
|
└─ Service: Maps to Player.name.asc(), Player.wara.desc()
|
|
└─ OK: Correct mapping
|
|
|
|
7. DEPENDENCY INJECTION INCOMPLETE
|
|
├─ Service imports: from ..db_engine import Player, Team
|
|
│ └─ ISSUE: Still uses direct model imports for filtering!
|
|
├─ Service uses: Player.team_id << team_id (Peewee query)
|
|
│ └─ ISSUE: This won't work with MockPlayerRepository!
|
|
└─ Service uses: peewee_fn.lower(Player.strat_code)
|
|
└─ ISSUE: This won't work with MockPlayerRepository!
|
|
└─ ISSUE: MockPlayerRepository doesn't support peewee_fn!
|
|
|
|
8. RESPONSE FIELD DIFFERENCES
|
|
├─ get_players: count + players [✓ match]
|
|
├─ get_one_player: returns dict or null [✓ match]
|
|
├─ search_players: count + players + all_seasons [✓ match]
|
|
├─ get_teams: count + teams [✓ match]
|
|
└─ get_one_team: returns dict or null [✓ match]
|
|
|
|
"""
|
|
|
|
# ============================================================================
|
|
# RECOMMENDED FIXES
|
|
# ============================================================================
|
|
|
|
"""
|
|
To make refactored code return EXACT SAME data:
|
|
|
|
1. FIX PLAYERSERVICE METHOD SIGNATURE
|
|
Current:
|
|
def get_players(self, season, team_id, pos, strat_code, name, ...):
|
|
|
|
Fix: Add @classmethod decorator
|
|
def get_players(cls, season, team_id, pos, strat_code, name, ...):
|
|
- Use cls instead of self
|
|
- Use Team.select() instead of self.team_repo
|
|
|
|
2. STANDARDIZE PARAMETER NAMES
|
|
Rename:
|
|
- as_csv → csv (to match router)
|
|
- short_output stays (both use same)
|
|
|
|
3. IMPLEMENT REPO-AGNOSTIC FILTERING
|
|
Current (broken):
|
|
query.where(Player.team_id << team_id)
|
|
|
|
Fix for Mock:
|
|
def _apply_filters(query, team_id, pos, strat_code, name, is_injured):
|
|
result = []
|
|
for item in query:
|
|
if team_id and item.get('team_id') not in team_id:
|
|
continue
|
|
if strat_code and item.get('strat_code', '').lower() not in [s.lower() for s in strat_code]:
|
|
continue
|
|
result.append(item)
|
|
return result
|
|
|
|
4. REMOVE DEPENDENCY ON peewee_fn IN SERVICE LAYER
|
|
Current:
|
|
query.where(peewee_fn.lower(Player.name) == name.lower())
|
|
|
|
Fix: Do string comparison in Python
|
|
for player in query:
|
|
if name and player.name.lower() != name.lower():
|
|
continue
|
|
|
|
5. REMOVE UNNECESSARY IMPORTS
|
|
Current in player_service.py:
|
|
from peewee import fn as peewee_fn
|
|
from ..db_engine import Player
|
|
|
|
These imports break the dependency injection pattern.
|
|
The service should ONLY use the repo interface.
|
|
"""
|
|
|
|
print(__doc__)
|