Files
domverse 6421f83ca7 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>
2026-02-26 17:11:51 +01:00

196 lines
5.4 KiB
Python

"""
Test fixtures and configuration for Flight Radar Web App tests.
This module provides reusable fixtures for testing the API.
"""
import pytest
import sqlite3
import os
import tempfile
from fastapi.testclient import TestClient
from typing import Generator
# Import the FastAPI app
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from api_server import app, rate_limiter, log_buffer
from database import get_connection
@pytest.fixture(scope="session")
def test_db_path() -> Generator[str, None, None]:
"""Create a temporary database for testing."""
# Create temporary database
fd, path = tempfile.mkstemp(suffix=".db")
os.close(fd)
# Set environment variable to use test database
original_db = os.environ.get('DATABASE_PATH')
os.environ['DATABASE_PATH'] = path
yield path
# Cleanup
if original_db:
os.environ['DATABASE_PATH'] = original_db
else:
os.environ.pop('DATABASE_PATH', None)
try:
os.unlink(path)
except OSError:
pass
@pytest.fixture(scope="function")
def clean_database(test_db_path):
"""Provide a clean database for each test."""
# Initialize database with schema
conn = sqlite3.connect(test_db_path)
conn.execute("PRAGMA foreign_keys = ON") # Enable foreign keys
cursor = conn.cursor()
# Read and execute schema
schema_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'database', 'schema.sql')
with open(schema_path, 'r') as f:
schema_sql = f.read()
cursor.executescript(schema_sql)
conn.commit()
conn.close()
yield test_db_path
# Clean up all tables after test
conn = sqlite3.connect(test_db_path)
conn.execute("PRAGMA foreign_keys = ON") # Enable foreign keys for cleanup
cursor = conn.cursor()
cursor.execute("DELETE FROM routes")
cursor.execute("DELETE FROM scans")
# Note: flight_searches and flight_results are not in web app schema
conn.commit()
conn.close()
@pytest.fixture(scope="function")
def client(clean_database) -> TestClient:
"""Provide a test client for the FastAPI app."""
# Clear rate limiter for each test
rate_limiter.requests.clear()
# Clear log buffer for each test
log_buffer.clear()
with TestClient(app) as test_client:
yield test_client
@pytest.fixture
def sample_scan_data():
"""Provide sample scan data for testing."""
return {
"origin": "BDS",
"country": "DE",
"start_date": "2026-04-01",
"end_date": "2026-06-30",
"seat_class": "economy",
"adults": 2
}
@pytest.fixture
def sample_route_data():
"""Provide sample route data for testing."""
return {
"scan_id": 1,
"destination": "MUC",
"destination_name": "Munich Airport",
"destination_city": "Munich",
"flight_count": 45,
"airlines": '["Lufthansa", "Ryanair"]',
"min_price": 89.99,
"max_price": 299.99,
"avg_price": 150.50
}
@pytest.fixture
def create_test_scan(clean_database, sample_scan_data):
"""Create a test scan in the database."""
def _create_scan(**kwargs):
# Merge with defaults
data = {**sample_scan_data, **kwargs}
conn = sqlite3.connect(clean_database)
conn.execute("PRAGMA foreign_keys = ON") # Enable foreign keys
cursor = conn.cursor()
cursor.execute("""
INSERT INTO scans (origin, country, start_date, end_date, status, seat_class, adults)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (
data['origin'],
data['country'],
data['start_date'],
data['end_date'],
data.get('status', 'pending'),
data['seat_class'],
data['adults']
))
scan_id = cursor.lastrowid
conn.commit()
conn.close()
return scan_id
return _create_scan
@pytest.fixture
def create_test_route(clean_database, sample_route_data):
"""Create a test route in the database."""
def _create_route(**kwargs):
# Merge with defaults
data = {**sample_route_data, **kwargs}
conn = sqlite3.connect(clean_database)
conn.execute("PRAGMA foreign_keys = ON") # Enable foreign keys
cursor = conn.cursor()
cursor.execute("""
INSERT INTO routes (scan_id, destination, destination_name, destination_city,
flight_count, airlines, min_price, max_price, avg_price)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
data['scan_id'],
data['destination'],
data['destination_name'],
data['destination_city'],
data['flight_count'],
data['airlines'],
data['min_price'],
data['max_price'],
data['avg_price']
))
route_id = cursor.lastrowid
conn.commit()
conn.close()
return route_id
return _create_route
# Marker helpers for categorizing tests
def pytest_configure(config):
"""Configure custom pytest markers."""
config.addinivalue_line("markers", "unit: Unit tests (fast, isolated)")
config.addinivalue_line("markers", "integration: Integration tests (slower)")
config.addinivalue_line("markers", "slow: Slow tests")
config.addinivalue_line("markers", "database: Tests that interact with database")
config.addinivalue_line("markers", "api: Tests for API endpoints")