mirror of
https://github.com/Xe138/AI-Trader.git
synced 2026-04-01 17:17:24 -04:00
feat: add date validation and resolution for results endpoint
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
from fastapi import APIRouter, Query, Depends
|
from fastapi import APIRouter, Query, Depends
|
||||||
from typing import Optional, Literal
|
from typing import Optional, Literal
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from api.database import Database
|
from api.database import Database
|
||||||
|
|
||||||
@@ -14,6 +16,65 @@ def get_database() -> Database:
|
|||||||
return Database()
|
return Database()
|
||||||
|
|
||||||
|
|
||||||
|
def validate_and_resolve_dates(
|
||||||
|
start_date: Optional[str],
|
||||||
|
end_date: Optional[str]
|
||||||
|
) -> tuple[str, str]:
|
||||||
|
"""Validate and resolve date parameters.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start_date: Start date (YYYY-MM-DD) or None
|
||||||
|
end_date: End date (YYYY-MM-DD) or None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (resolved_start_date, resolved_end_date)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If dates are invalid
|
||||||
|
"""
|
||||||
|
# Default lookback days
|
||||||
|
default_lookback = int(os.getenv("DEFAULT_RESULTS_LOOKBACK_DAYS", "30"))
|
||||||
|
|
||||||
|
# Handle None cases
|
||||||
|
if start_date is None and end_date is None:
|
||||||
|
# Default to last N days
|
||||||
|
end_dt = datetime.now()
|
||||||
|
start_dt = end_dt - timedelta(days=default_lookback)
|
||||||
|
return start_dt.strftime("%Y-%m-%d"), end_dt.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
if start_date is None:
|
||||||
|
# Only end_date provided -> single date
|
||||||
|
start_date = end_date
|
||||||
|
|
||||||
|
if end_date is None:
|
||||||
|
# Only start_date provided -> single date
|
||||||
|
end_date = start_date
|
||||||
|
|
||||||
|
# Validate date formats
|
||||||
|
try:
|
||||||
|
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
|
||||||
|
end_dt = datetime.strptime(end_date, "%Y-%m-%d")
|
||||||
|
|
||||||
|
# Ensure strict YYYY-MM-DD format (e.g., reject "2025-1-16")
|
||||||
|
if start_date != start_dt.strftime("%Y-%m-%d"):
|
||||||
|
raise ValueError(f"Invalid date format. Expected YYYY-MM-DD")
|
||||||
|
if end_date != end_dt.strftime("%Y-%m-%d"):
|
||||||
|
raise ValueError(f"Invalid date format. Expected YYYY-MM-DD")
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f"Invalid date format. Expected YYYY-MM-DD")
|
||||||
|
|
||||||
|
# Validate order
|
||||||
|
if start_dt > end_dt:
|
||||||
|
raise ValueError("start_date must be <= end_date")
|
||||||
|
|
||||||
|
# Validate not future
|
||||||
|
now = datetime.now()
|
||||||
|
if start_dt.date() > now.date() or end_dt.date() > now.date():
|
||||||
|
raise ValueError("Cannot query future dates")
|
||||||
|
|
||||||
|
return start_date, end_date
|
||||||
|
|
||||||
|
|
||||||
@router.get("/results")
|
@router.get("/results")
|
||||||
async def get_results(
|
async def get_results(
|
||||||
job_id: Optional[str] = None,
|
job_id: Optional[str] = None,
|
||||||
|
|||||||
61
tests/api/test_results_v2.py
Normal file
61
tests/api/test_results_v2.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
"""Tests for results_v2 endpoint date validation."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from api.routes.results_v2 import validate_and_resolve_dates
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_no_dates_provided():
|
||||||
|
"""Test default to last 30 days when no dates provided."""
|
||||||
|
start, end = validate_and_resolve_dates(None, None)
|
||||||
|
|
||||||
|
# Should default to last 30 days
|
||||||
|
end_dt = datetime.strptime(end, "%Y-%m-%d")
|
||||||
|
start_dt = datetime.strptime(start, "%Y-%m-%d")
|
||||||
|
|
||||||
|
assert (end_dt - start_dt).days == 30
|
||||||
|
assert end_dt.date() <= datetime.now().date()
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_only_start_date():
|
||||||
|
"""Test single date when only start_date provided."""
|
||||||
|
start, end = validate_and_resolve_dates("2025-01-16", None)
|
||||||
|
|
||||||
|
assert start == "2025-01-16"
|
||||||
|
assert end == "2025-01-16"
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_only_end_date():
|
||||||
|
"""Test single date when only end_date provided."""
|
||||||
|
start, end = validate_and_resolve_dates(None, "2025-01-16")
|
||||||
|
|
||||||
|
assert start == "2025-01-16"
|
||||||
|
assert end == "2025-01-16"
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_both_dates():
|
||||||
|
"""Test date range when both provided."""
|
||||||
|
start, end = validate_and_resolve_dates("2025-01-16", "2025-01-20")
|
||||||
|
|
||||||
|
assert start == "2025-01-16"
|
||||||
|
assert end == "2025-01-20"
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_invalid_date_format():
|
||||||
|
"""Test error on invalid date format."""
|
||||||
|
with pytest.raises(ValueError, match="Invalid date format"):
|
||||||
|
validate_and_resolve_dates("2025-1-16", "2025-01-20")
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_start_after_end():
|
||||||
|
"""Test error when start_date > end_date."""
|
||||||
|
with pytest.raises(ValueError, match="start_date must be <= end_date"):
|
||||||
|
validate_and_resolve_dates("2025-01-20", "2025-01-16")
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_future_date():
|
||||||
|
"""Test error when dates are in future."""
|
||||||
|
future = (datetime.now() + timedelta(days=10)).strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Cannot query future dates"):
|
||||||
|
validate_and_resolve_dates(future, future)
|
||||||
Reference in New Issue
Block a user