mirror of
https://github.com/sstent/GarminSync.git
synced 2026-01-26 00:52:32 +00:00
working again stable
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import JSONResponse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from .routes import router
|
||||
|
||||
app = FastAPI(title="GarminSync Dashboard")
|
||||
@@ -26,73 +28,80 @@ else:
|
||||
# Include API routes
|
||||
app.include_router(router)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def dashboard(request: Request):
|
||||
"""Dashboard route with fallback for missing templates"""
|
||||
if not templates:
|
||||
# Return JSON response if templates are not available
|
||||
from garminsync.database import get_offline_stats
|
||||
|
||||
stats = get_offline_stats()
|
||||
return JSONResponse({
|
||||
"message": "GarminSync Dashboard",
|
||||
"stats": stats,
|
||||
"note": "Web UI templates not found, showing JSON response"
|
||||
})
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"message": "GarminSync Dashboard",
|
||||
"stats": stats,
|
||||
"note": "Web UI templates not found, showing JSON response",
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
# Get current statistics
|
||||
from garminsync.database import get_offline_stats
|
||||
|
||||
stats = get_offline_stats()
|
||||
|
||||
return templates.TemplateResponse("dashboard.html", {
|
||||
"request": request,
|
||||
"stats": stats
|
||||
})
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"dashboard.html", {"request": request, "stats": stats}
|
||||
)
|
||||
except Exception as e:
|
||||
return JSONResponse({
|
||||
"error": f"Failed to load dashboard: {str(e)}",
|
||||
"message": "Dashboard unavailable, API endpoints still functional"
|
||||
})
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": f"Failed to load dashboard: {str(e)}",
|
||||
"message": "Dashboard unavailable, API endpoints still functional",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "healthy", "service": "GarminSync Dashboard"}
|
||||
|
||||
|
||||
@app.get("/config")
|
||||
async def config_page(request: Request):
|
||||
"""Configuration page"""
|
||||
if not templates:
|
||||
return JSONResponse({
|
||||
"message": "Configuration endpoint",
|
||||
"note": "Use /api/schedule endpoints for configuration"
|
||||
})
|
||||
|
||||
return templates.TemplateResponse("config.html", {
|
||||
"request": request
|
||||
})
|
||||
return JSONResponse(
|
||||
{
|
||||
"message": "Configuration endpoint",
|
||||
"note": "Use /api/schedule endpoints for configuration",
|
||||
}
|
||||
)
|
||||
|
||||
return templates.TemplateResponse("config.html", {"request": request})
|
||||
|
||||
|
||||
@app.get("/activities")
|
||||
async def activities_page(request: Request):
|
||||
"""Activities page route"""
|
||||
if not templates:
|
||||
return JSONResponse({"message": "Activities endpoint"})
|
||||
|
||||
return templates.TemplateResponse("activities.html", {
|
||||
"request": request
|
||||
})
|
||||
|
||||
return templates.TemplateResponse("activities.html", {"request": request})
|
||||
|
||||
|
||||
# Error handlers
|
||||
@app.exception_handler(404)
|
||||
async def not_found_handler(request: Request, exc):
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"error": "Not found", "path": str(request.url.path)}
|
||||
status_code=404, content={"error": "Not found", "path": str(request.url.path)}
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(500)
|
||||
async def server_error_handler(request: Request, exc):
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"error": "Internal server error", "detail": str(exc)}
|
||||
status_code=500, content={"error": "Internal server error", "detail": str(exc)}
|
||||
)
|
||||
|
||||
@@ -1,144 +1,159 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from garminsync.database import get_session, DaemonConfig, SyncLog, Activity
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from garminsync.database import Activity, DaemonConfig, SyncLog, get_session
|
||||
|
||||
router = APIRouter(prefix="/api")
|
||||
|
||||
|
||||
class ScheduleConfig(BaseModel):
|
||||
enabled: bool
|
||||
cron_schedule: str
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
async def get_status():
|
||||
"""Get current daemon status"""
|
||||
session = get_session()
|
||||
try:
|
||||
config = session.query(DaemonConfig).first()
|
||||
|
||||
|
||||
# Get recent logs
|
||||
logs = session.query(SyncLog).order_by(SyncLog.timestamp.desc()).limit(10).all()
|
||||
|
||||
|
||||
# Convert to dictionaries to avoid session issues
|
||||
daemon_data = {
|
||||
"running": config.status == "running" if config else False,
|
||||
"next_run": config.next_run if config else None,
|
||||
"schedule": config.schedule_cron if config else None,
|
||||
"last_run": config.last_run if config else None,
|
||||
"enabled": config.enabled if config else False
|
||||
"enabled": config.enabled if config else False,
|
||||
}
|
||||
|
||||
|
||||
log_data = []
|
||||
for log in logs:
|
||||
log_data.append({
|
||||
"timestamp": log.timestamp,
|
||||
"operation": log.operation,
|
||||
"status": log.status,
|
||||
"message": log.message,
|
||||
"activities_processed": log.activities_processed,
|
||||
"activities_downloaded": log.activities_downloaded
|
||||
})
|
||||
|
||||
return {
|
||||
"daemon": daemon_data,
|
||||
"recent_logs": log_data
|
||||
}
|
||||
log_data.append(
|
||||
{
|
||||
"timestamp": log.timestamp,
|
||||
"operation": log.operation,
|
||||
"status": log.status,
|
||||
"message": log.message,
|
||||
"activities_processed": log.activities_processed,
|
||||
"activities_downloaded": log.activities_downloaded,
|
||||
}
|
||||
)
|
||||
|
||||
return {"daemon": daemon_data, "recent_logs": log_data}
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@router.post("/schedule")
|
||||
async def update_schedule(config: ScheduleConfig):
|
||||
"""Update daemon schedule configuration"""
|
||||
session = get_session()
|
||||
try:
|
||||
daemon_config = session.query(DaemonConfig).first()
|
||||
|
||||
|
||||
if not daemon_config:
|
||||
daemon_config = DaemonConfig()
|
||||
session.add(daemon_config)
|
||||
|
||||
|
||||
daemon_config.enabled = config.enabled
|
||||
daemon_config.schedule_cron = config.cron_schedule
|
||||
session.commit()
|
||||
|
||||
|
||||
return {"message": "Configuration updated successfully"}
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"Failed to update configuration: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Failed to update configuration: {str(e)}"
|
||||
)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@router.post("/sync/trigger")
|
||||
async def trigger_sync():
|
||||
"""Manually trigger a sync operation"""
|
||||
try:
|
||||
# Import here to avoid circular imports
|
||||
from garminsync.garmin import GarminClient
|
||||
from garminsync.database import sync_database, Activity
|
||||
from datetime import datetime
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
from garminsync.database import Activity, sync_database
|
||||
from garminsync.garmin import GarminClient
|
||||
|
||||
# Create client and sync
|
||||
client = GarminClient()
|
||||
sync_database(client)
|
||||
|
||||
|
||||
# Download missing activities
|
||||
session = get_session()
|
||||
try:
|
||||
missing_activities = session.query(Activity).filter_by(downloaded=False).all()
|
||||
missing_activities = (
|
||||
session.query(Activity).filter_by(downloaded=False).all()
|
||||
)
|
||||
downloaded_count = 0
|
||||
|
||||
|
||||
data_dir = Path(os.getenv("DATA_DIR", "data"))
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
for activity in missing_activities:
|
||||
try:
|
||||
fit_data = client.download_activity_fit(activity.activity_id)
|
||||
|
||||
|
||||
timestamp = activity.start_time.replace(":", "-").replace(" ", "_")
|
||||
filename = f"activity_{activity.activity_id}_{timestamp}.fit"
|
||||
filepath = data_dir / filename
|
||||
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(fit_data)
|
||||
|
||||
|
||||
activity.filename = str(filepath)
|
||||
activity.downloaded = True
|
||||
activity.last_sync = datetime.now().isoformat()
|
||||
downloaded_count += 1
|
||||
session.commit()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to download activity {activity.activity_id}: {e}")
|
||||
session.rollback()
|
||||
|
||||
return {"message": f"Sync completed successfully. Downloaded {downloaded_count} activities."}
|
||||
|
||||
return {
|
||||
"message": f"Sync completed successfully. Downloaded {downloaded_count} activities."
|
||||
}
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Sync failed: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/activities/stats")
|
||||
async def get_activity_stats():
|
||||
"""Get activity statistics"""
|
||||
from garminsync.database import get_offline_stats
|
||||
|
||||
return get_offline_stats()
|
||||
|
||||
|
||||
@router.get("/logs")
|
||||
async def get_logs(
|
||||
status: str = None,
|
||||
operation: str = None,
|
||||
date: str = None,
|
||||
page: int = 1,
|
||||
per_page: int = 20
|
||||
per_page: int = 20,
|
||||
):
|
||||
"""Get sync logs with filtering and pagination"""
|
||||
session = get_session()
|
||||
try:
|
||||
query = session.query(SyncLog)
|
||||
|
||||
|
||||
# Apply filters
|
||||
if status:
|
||||
query = query.filter(SyncLog.status == status)
|
||||
@@ -147,48 +162,50 @@ async def get_logs(
|
||||
if date:
|
||||
# Filter by date (assuming ISO format)
|
||||
query = query.filter(SyncLog.timestamp.like(f"{date}%"))
|
||||
|
||||
|
||||
# Get total count for pagination
|
||||
total = query.count()
|
||||
|
||||
|
||||
# Apply pagination
|
||||
logs = query.order_by(SyncLog.timestamp.desc()) \
|
||||
.offset((page - 1) * per_page) \
|
||||
.limit(per_page) \
|
||||
.all()
|
||||
|
||||
logs = (
|
||||
query.order_by(SyncLog.timestamp.desc())
|
||||
.offset((page - 1) * per_page)
|
||||
.limit(per_page)
|
||||
.all()
|
||||
)
|
||||
|
||||
log_data = []
|
||||
for log in logs:
|
||||
log_data.append({
|
||||
"id": log.id,
|
||||
"timestamp": log.timestamp,
|
||||
"operation": log.operation,
|
||||
"status": log.status,
|
||||
"message": log.message,
|
||||
"activities_processed": log.activities_processed,
|
||||
"activities_downloaded": log.activities_downloaded
|
||||
})
|
||||
|
||||
return {
|
||||
"logs": log_data,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": per_page
|
||||
}
|
||||
log_data.append(
|
||||
{
|
||||
"id": log.id,
|
||||
"timestamp": log.timestamp,
|
||||
"operation": log.operation,
|
||||
"status": log.status,
|
||||
"message": log.message,
|
||||
"activities_processed": log.activities_processed,
|
||||
"activities_downloaded": log.activities_downloaded,
|
||||
}
|
||||
)
|
||||
|
||||
return {"logs": log_data, "total": total, "page": page, "per_page": per_page}
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@router.post("/daemon/start")
|
||||
async def start_daemon():
|
||||
"""Start the daemon process"""
|
||||
from garminsync.daemon import daemon_instance
|
||||
|
||||
try:
|
||||
# Start the daemon in a separate thread to avoid blocking
|
||||
import threading
|
||||
|
||||
daemon_thread = threading.Thread(target=daemon_instance.start)
|
||||
daemon_thread.daemon = True
|
||||
daemon_thread.start()
|
||||
|
||||
|
||||
# Update daemon status in database
|
||||
session = get_session()
|
||||
config = session.query(DaemonConfig).first()
|
||||
@@ -197,7 +214,7 @@ async def start_daemon():
|
||||
session.add(config)
|
||||
config.status = "running"
|
||||
session.commit()
|
||||
|
||||
|
||||
return {"message": "Daemon started successfully"}
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
@@ -205,21 +222,23 @@ async def start_daemon():
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@router.post("/daemon/stop")
|
||||
async def stop_daemon():
|
||||
"""Stop the daemon process"""
|
||||
from garminsync.daemon import daemon_instance
|
||||
|
||||
try:
|
||||
# Stop the daemon
|
||||
daemon_instance.stop()
|
||||
|
||||
|
||||
# Update daemon status in database
|
||||
session = get_session()
|
||||
config = session.query(DaemonConfig).first()
|
||||
if config:
|
||||
config.status = "stopped"
|
||||
session.commit()
|
||||
|
||||
|
||||
return {"message": "Daemon stopped successfully"}
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
@@ -227,6 +246,7 @@ async def stop_daemon():
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@router.delete("/logs")
|
||||
async def clear_logs():
|
||||
"""Clear all sync logs"""
|
||||
@@ -241,19 +261,20 @@ async def clear_logs():
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@router.get("/activities")
|
||||
async def get_activities(
|
||||
page: int = 1,
|
||||
per_page: int = 50,
|
||||
activity_type: str = None,
|
||||
date_from: str = None,
|
||||
date_to: str = None
|
||||
date_to: str = None,
|
||||
):
|
||||
"""Get paginated activities with filtering"""
|
||||
session = get_session()
|
||||
try:
|
||||
query = session.query(Activity)
|
||||
|
||||
|
||||
# Apply filters
|
||||
if activity_type:
|
||||
query = query.filter(Activity.activity_type == activity_type)
|
||||
@@ -261,70 +282,147 @@ async def get_activities(
|
||||
query = query.filter(Activity.start_time >= date_from)
|
||||
if date_to:
|
||||
query = query.filter(Activity.start_time <= date_to)
|
||||
|
||||
|
||||
# Get total count for pagination
|
||||
total = query.count()
|
||||
|
||||
|
||||
# Apply pagination
|
||||
activities = query.order_by(Activity.start_time.desc()) \
|
||||
.offset((page - 1) * per_page) \
|
||||
.limit(per_page) \
|
||||
.all()
|
||||
|
||||
activities = (
|
||||
query.order_by(Activity.start_time.desc())
|
||||
.offset((page - 1) * per_page)
|
||||
.limit(per_page)
|
||||
.all()
|
||||
)
|
||||
|
||||
activity_data = []
|
||||
for activity in activities:
|
||||
activity_data.append({
|
||||
"activity_id": activity.activity_id,
|
||||
"start_time": activity.start_time,
|
||||
"activity_type": activity.activity_type,
|
||||
"duration": activity.duration,
|
||||
"distance": activity.distance,
|
||||
"max_heart_rate": activity.max_heart_rate,
|
||||
"avg_power": activity.avg_power,
|
||||
"calories": activity.calories,
|
||||
"filename": activity.filename,
|
||||
"downloaded": activity.downloaded,
|
||||
"created_at": activity.created_at,
|
||||
"last_sync": activity.last_sync
|
||||
})
|
||||
|
||||
activity_data.append(
|
||||
{
|
||||
"activity_id": activity.activity_id,
|
||||
"start_time": activity.start_time,
|
||||
"activity_type": activity.activity_type,
|
||||
"duration": activity.duration,
|
||||
"distance": activity.distance,
|
||||
"max_heart_rate": activity.max_heart_rate,
|
||||
"avg_power": activity.avg_power,
|
||||
"calories": activity.calories,
|
||||
"filename": activity.filename,
|
||||
"downloaded": activity.downloaded,
|
||||
"created_at": activity.created_at,
|
||||
"last_sync": activity.last_sync,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"activities": activity_data,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": per_page
|
||||
"per_page": per_page,
|
||||
}
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@router.get("/activities/{activity_id}")
|
||||
async def get_activity_details(activity_id: int):
|
||||
"""Get detailed activity information"""
|
||||
session = get_session()
|
||||
try:
|
||||
activity = session.query(Activity).filter(Activity.activity_id == activity_id).first()
|
||||
activity = (
|
||||
session.query(Activity).filter(Activity.activity_id == activity_id).first()
|
||||
)
|
||||
if not activity:
|
||||
raise HTTPException(status_code=404, detail="Activity not found")
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Activity with ID {activity_id} not found"
|
||||
)
|
||||
|
||||
return {
|
||||
"activity_id": activity.activity_id,
|
||||
"id": activity.activity_id,
|
||||
"name": activity.filename or "Unnamed Activity",
|
||||
"distance": activity.distance,
|
||||
"duration": activity.duration,
|
||||
"start_time": activity.start_time,
|
||||
"activity_type": activity.activity_type,
|
||||
"duration": activity.duration,
|
||||
"distance": activity.distance,
|
||||
"max_heart_rate": activity.max_heart_rate,
|
||||
"avg_power": activity.avg_power,
|
||||
"calories": activity.calories,
|
||||
"filename": activity.filename,
|
||||
"downloaded": activity.downloaded,
|
||||
"created_at": activity.created_at,
|
||||
"last_sync": activity.last_sync
|
||||
"last_sync": activity.last_sync,
|
||||
}
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@router.get("/dashboard/stats")
|
||||
async def get_dashboard_stats():
|
||||
"""Get comprehensive dashboard statistics"""
|
||||
from garminsync.database import get_offline_stats
|
||||
|
||||
return get_offline_stats()
|
||||
|
||||
|
||||
@router.get("/api/activities")
|
||||
async def get_api_activities(page: int = 1, per_page: int = 10):
|
||||
"""Get paginated activities for API"""
|
||||
session = get_session()
|
||||
try:
|
||||
# Use the existing get_paginated method from Activity class
|
||||
pagination = Activity.get_paginated(page, per_page)
|
||||
activities = pagination.items
|
||||
total_pages = pagination.pages
|
||||
current_page = pagination.page
|
||||
total_items = pagination.total
|
||||
|
||||
if not activities and page > 1:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"No activities found for page {page}"
|
||||
)
|
||||
|
||||
if not activities and page == 1 and total_items == 0:
|
||||
raise HTTPException(status_code=404, detail="No activities found")
|
||||
|
||||
if not activities:
|
||||
raise HTTPException(status_code=404, detail="No activities found")
|
||||
|
||||
return {
|
||||
"activities": [
|
||||
{
|
||||
"id": activity.activity_id,
|
||||
"name": activity.filename or "Unnamed Activity",
|
||||
"distance": activity.distance,
|
||||
"duration": activity.duration,
|
||||
"start_time": activity.start_time,
|
||||
"activity_type": activity.activity_type,
|
||||
"max_heart_rate": activity.max_heart_rate,
|
||||
"avg_power": activity.avg_power,
|
||||
"calories": activity.calories,
|
||||
"downloaded": activity.downloaded,
|
||||
"created_at": activity.created_at,
|
||||
"last_sync": activity.last_sync,
|
||||
"device": activity.device or "Unknown",
|
||||
"intensity": activity.intensity or "Unknown",
|
||||
"average_speed": activity.average_speed,
|
||||
"elevation_gain": activity.elevation_gain,
|
||||
"heart_rate_zones": activity.heart_rate_zones or [],
|
||||
"power_zones": activity.power_zones or [],
|
||||
"training_effect": activity.training_effect or 0,
|
||||
"training_effect_label": activity.training_effect_label
|
||||
or "Unknown",
|
||||
}
|
||||
for activity in activities
|
||||
],
|
||||
"total_pages": total_pages,
|
||||
"current_page": current_page,
|
||||
"total_items": total_items,
|
||||
"page_size": per_page,
|
||||
"status": "success",
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"An error occurred while fetching activities: {str(e)}",
|
||||
)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
@@ -3,18 +3,20 @@
|
||||
Simple test script to verify the new UI is working correctly
|
||||
"""
|
||||
|
||||
import requests
|
||||
import time
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
# Add the parent directory to the path to import garminsync modules
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
|
||||
def test_ui_endpoints():
|
||||
"""Test that the new UI endpoints are working correctly"""
|
||||
base_url = "http://localhost:8000"
|
||||
|
||||
|
||||
# Test endpoints to check
|
||||
endpoints = [
|
||||
"/",
|
||||
@@ -23,26 +25,26 @@ def test_ui_endpoints():
|
||||
"/logs",
|
||||
"/api/status",
|
||||
"/api/activities/stats",
|
||||
"/api/dashboard/stats"
|
||||
"/api/dashboard/stats",
|
||||
]
|
||||
|
||||
|
||||
print("Testing UI endpoints...")
|
||||
|
||||
|
||||
failed_endpoints = []
|
||||
|
||||
|
||||
for endpoint in endpoints:
|
||||
try:
|
||||
url = base_url + endpoint
|
||||
print(f"Testing {url}...")
|
||||
|
||||
|
||||
response = requests.get(url, timeout=10)
|
||||
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f" ✓ {endpoint} - OK")
|
||||
else:
|
||||
print(f" ✗ {endpoint} - Status code: {response.status_code}")
|
||||
failed_endpoints.append(endpoint)
|
||||
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(f" ✗ {endpoint} - Connection error (server not running?)")
|
||||
failed_endpoints.append(endpoint)
|
||||
@@ -52,7 +54,7 @@ def test_ui_endpoints():
|
||||
except Exception as e:
|
||||
print(f" ✗ {endpoint} - Error: {e}")
|
||||
failed_endpoints.append(endpoint)
|
||||
|
||||
|
||||
if failed_endpoints:
|
||||
print(f"\nFailed endpoints: {failed_endpoints}")
|
||||
return False
|
||||
@@ -60,45 +62,51 @@ def test_ui_endpoints():
|
||||
print("\nAll endpoints are working correctly!")
|
||||
return True
|
||||
|
||||
|
||||
def test_api_endpoints():
|
||||
"""Test that the new API endpoints are working correctly"""
|
||||
base_url = "http://localhost:8000"
|
||||
|
||||
|
||||
# Test API endpoints
|
||||
api_endpoints = [
|
||||
("/api/activities", "GET"),
|
||||
("/api/activities/1", "GET"), # This might fail if activity doesn't exist, which is OK
|
||||
("/api/dashboard/stats", "GET")
|
||||
(
|
||||
"/api/activities/1",
|
||||
"GET",
|
||||
), # This might fail if activity doesn't exist, which is OK
|
||||
("/api/dashboard/stats", "GET"),
|
||||
]
|
||||
|
||||
|
||||
print("\nTesting API endpoints...")
|
||||
|
||||
|
||||
for endpoint, method in api_endpoints:
|
||||
try:
|
||||
url = base_url + endpoint
|
||||
print(f"Testing {method} {url}...")
|
||||
|
||||
|
||||
if method == "GET":
|
||||
response = requests.get(url, timeout=10)
|
||||
else:
|
||||
response = requests.post(url, timeout=10)
|
||||
|
||||
|
||||
# For activity details, 404 is acceptable if activity doesn't exist
|
||||
if endpoint == "/api/activities/1" and response.status_code == 404:
|
||||
print(f" ✓ {endpoint} - OK (404 expected if activity doesn't exist)")
|
||||
continue
|
||||
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f" ✓ {endpoint} - OK")
|
||||
# Try to parse JSON
|
||||
try:
|
||||
data = response.json()
|
||||
print(f" Response keys: {list(data.keys()) if isinstance(data, dict) else 'Not a dict'}")
|
||||
print(
|
||||
f" Response keys: {list(data.keys()) if isinstance(data, dict) else 'Not a dict'}"
|
||||
)
|
||||
except:
|
||||
print(" Response is not JSON")
|
||||
else:
|
||||
print(f" ✗ {endpoint} - Status code: {response.status_code}")
|
||||
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(f" ✗ {endpoint} - Connection error (server not running?)")
|
||||
except requests.exceptions.Timeout:
|
||||
@@ -106,16 +114,17 @@ def test_api_endpoints():
|
||||
except Exception as e:
|
||||
print(f" ✗ {endpoint} - Error: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("GarminSync UI Test Script")
|
||||
print("=" * 30)
|
||||
|
||||
|
||||
# Test UI endpoints
|
||||
ui_success = test_ui_endpoints()
|
||||
|
||||
|
||||
# Test API endpoints
|
||||
test_api_endpoints()
|
||||
|
||||
|
||||
print("\n" + "=" * 30)
|
||||
if ui_success:
|
||||
print("UI tests completed successfully!")
|
||||
|
||||
Reference in New Issue
Block a user