Add flight comparator web app with full scan pipeline
Full-stack flight price scanner built on fast-flights v3 (SOCS cookie bypass): Backend (FastAPI + SQLite): - REST API with rate limiting, Pydantic v2 validation, paginated responses - Scan pipeline: resolves airports, queries every day in the window, saves individual flights + aggregate route stats to SQLite - Background async scan processor with real-time progress tracking - Airport search endpoint backed by OpenFlights dataset - Daily scan window (all dates, not monthly samples) Frontend (React 19 + TypeScript + Tailwind CSS v4): - Dashboard with live scan status and recent scans - Create scan form: country mode or specific airports (searchable dropdown) - Scan detail page with expandable route rows showing individual flights (date, airline, departure, arrival, price) loaded on demand - AirportSearch component with debounced live search and multi-select Database: - scans → routes → flights schema with FK cascade and auto-update triggers - Migrations for schema evolution (relaxed country constraint) Tests: - 74 tests: unit + integration, isolated per-test SQLite DB - Confirmed flight fixtures in tests/confirmed_flights.json (50 real flights, BDS→FMM Ryanair + BDS→DUS Eurowings, scraped Feb 2026) - Integration tests parametrized from confirmed routes Docker: - Multi-stage builds, Compose orchestration, Nginx reverse proxy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
64
flight-comparator/tests/test_date_resolver.py
Normal file
64
flight-comparator/tests/test_date_resolver.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Smoke tests for date_resolver module.
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import sys
|
||||
sys.path.insert(0, '..')
|
||||
|
||||
from date_resolver import resolve_dates, detect_new_connections, SEARCH_WINDOW_MONTHS
|
||||
|
||||
|
||||
def test_resolve_dates_with_specific_date():
|
||||
"""Test that a specific date returns only that date."""
|
||||
result = resolve_dates("2026-06-15", 6)
|
||||
assert result == ["2026-06-15"]
|
||||
print("✓ Specific date resolution works")
|
||||
|
||||
|
||||
def test_resolve_dates_seasonal():
|
||||
"""Test that seasonal mode generates one date per month."""
|
||||
result = resolve_dates(None, 3)
|
||||
assert len(result) == 3
|
||||
# All should be valid date strings
|
||||
for date_str in result:
|
||||
assert len(date_str) == 10 # YYYY-MM-DD format
|
||||
assert date_str.count('-') == 2
|
||||
print(f"✓ Seasonal resolution works: {result}")
|
||||
|
||||
|
||||
def test_detect_new_connections():
|
||||
"""Test new connection detection logic."""
|
||||
monthly_results = {
|
||||
"2026-03": [
|
||||
{"origin": "FRA", "destination": "JFK"},
|
||||
{"origin": "MUC", "destination": "JFK"},
|
||||
],
|
||||
"2026-04": [
|
||||
{"origin": "FRA", "destination": "JFK"},
|
||||
{"origin": "MUC", "destination": "JFK"},
|
||||
{"origin": "BER", "destination": "JFK"}, # NEW
|
||||
],
|
||||
"2026-05": [
|
||||
{"origin": "FRA", "destination": "JFK"},
|
||||
{"origin": "BER", "destination": "JFK"},
|
||||
{"origin": "HAM", "destination": "JFK"}, # NEW
|
||||
],
|
||||
}
|
||||
|
||||
new = detect_new_connections(monthly_results)
|
||||
assert "BER->JFK" in new
|
||||
assert new["BER->JFK"] == "2026-04"
|
||||
assert "HAM->JFK" in new
|
||||
assert new["HAM->JFK"] == "2026-05"
|
||||
assert "FRA->JFK" not in new # Was in first month
|
||||
assert "MUC->JFK" not in new # Was in first month
|
||||
print(f"✓ New connection detection works: {new}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_resolve_dates_with_specific_date()
|
||||
test_resolve_dates_seasonal()
|
||||
test_detect_new_connections()
|
||||
print("\n✅ All date_resolver tests passed!")
|
||||
Reference in New Issue
Block a user