mirror of
https://github.com/sstent/FitTrack_ReportGenerator.git
synced 2026-01-27 09:32:12 +00:00
feat: Initial implementation of FitTrack Report Generator
This commit introduces the initial version of the FitTrack Report Generator, a FastAPI application for analyzing workout files. Key features include: - Parsing of FIT, TCX, and GPX workout files. - Analysis of power, heart rate, speed, and elevation data. - Generation of summary reports and charts. - REST API for single and batch workout analysis. The project structure has been set up with a `src` directory for core logic, an `api` directory for the FastAPI application, and a `tests` directory for unit, integration, and contract tests. The development workflow is configured to use Docker and modern Python tooling.
This commit is contained in:
101
tests/contract/test_batch_analysis_api.py
Normal file
101
tests/contract/test_batch_analysis_api.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from api.main import app
|
||||
from uuid import uuid4
|
||||
from unittest.mock import patch
|
||||
import zipfile
|
||||
import io
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
def create_zip_file(file_names_and_content):
|
||||
zip_buffer = io.BytesIO()
|
||||
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for name, content in file_names_and_content.items():
|
||||
zf.writestr(name, content)
|
||||
zip_buffer.seek(0)
|
||||
return zip_buffer
|
||||
|
||||
@patch('src.db.session.get_db')
|
||||
@patch('src.core.batch_processor.BatchProcessor')
|
||||
def test_analyze_batch_success(mock_batch_processor_cls, mock_get_db):
|
||||
mock_db_session = mock_get_db.return_value
|
||||
mock_batch_processor_instance = mock_batch_processor_cls.return_value
|
||||
|
||||
mock_batch_processor_instance.process_zip_file.return_value = [
|
||||
{"analysis_id": str(uuid4()), "file_name": "workout1.fit", "status": "completed"},
|
||||
{"analysis_id": str(uuid4()), "file_name": "workout2.tcx", "status": "completed"}
|
||||
]
|
||||
|
||||
zip_content = create_zip_file({"workout1.fit": b"dummy_fit_content", "workout2.tcx": b"dummy_tcx_content"})
|
||||
|
||||
response = client.post(
|
||||
"/api/analyze/batch",
|
||||
files={"zip_file": ("workouts.zip", zip_content.getvalue(), "application/zip")},
|
||||
data={
|
||||
"user_id": str(uuid4()),
|
||||
"ftp_value": 250.0
|
||||
}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
response_json = response.json()
|
||||
assert "batch_id" in response_json
|
||||
assert response_json["status"] == "completed"
|
||||
assert response_json["total_files"] == 2
|
||||
assert "results" in response_json
|
||||
assert len(response_json["results"]) == 2
|
||||
assert mock_batch_processor_instance.process_zip_file.called
|
||||
|
||||
@patch('src.db.session.get_db')
|
||||
@patch('src.core.batch_processor.BatchProcessor')
|
||||
def test_analyze_batch_empty_zip(mock_batch_processor_cls, mock_get_db):
|
||||
zip_content = create_zip_file({})
|
||||
|
||||
response = client.post(
|
||||
"/api/analyze/batch",
|
||||
files={"zip_file": ("empty.zip", zip_content.getvalue(), "application/zip")}
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json()["code"] == "EMPTY_ZIP_FILE"
|
||||
|
||||
@patch('src.db.session.get_db')
|
||||
@patch('src.core.batch_processor.BatchProcessor')
|
||||
def test_analyze_batch_partial_failure(mock_batch_processor_cls, mock_get_db):
|
||||
mock_db_session = mock_get_db.return_value
|
||||
mock_batch_processor_instance = mock_batch_processor_cls.return_value
|
||||
|
||||
mock_batch_processor_instance.process_zip_file.return_value = [
|
||||
{"analysis_id": str(uuid4()), "file_name": "workout1.fit", "status": "completed"},
|
||||
{"file_name": "workout_bad.fit", "status": "failed", "error_message": "Corrupted file"}
|
||||
]
|
||||
|
||||
zip_content = create_zip_file({"workout1.fit": b"dummy_fit_content", "workout_bad.fit": b"bad_content"})
|
||||
|
||||
response = client.post(
|
||||
"/api/analyze/batch",
|
||||
files={"zip_file": ("workouts.zip", zip_content.getvalue(), "application/zip")}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
response_json = response.json()
|
||||
assert response_json["status"] == "completed_with_errors"
|
||||
assert response_json["total_files"] == 2
|
||||
assert len(response_json["results"]) == 2
|
||||
assert any(r["status"] == "failed" for r in response_json["results"])
|
||||
|
||||
@patch('src.db.session.get_db')
|
||||
@patch('src.core.batch_processor.BatchProcessor')
|
||||
def test_analyze_batch_internal_error(mock_batch_processor_cls, mock_get_db):
|
||||
mock_batch_processor_cls.side_effect = Exception("Unexpected error")
|
||||
|
||||
zip_content = create_zip_file({"workout1.fit": b"dummy_fit_content"})
|
||||
|
||||
response = client.post(
|
||||
"/api/analyze/batch",
|
||||
files={"zip_file": ("workouts.zip", zip_content.getvalue(), "application/zip")}
|
||||
)
|
||||
|
||||
assert response.status_code == 500
|
||||
assert response.json()["code"] == "INTERNAL_SERVER_ERROR"
|
||||
Reference in New Issue
Block a user