refactor: split monolithic app.py into modular Flask application with factory pattern

- Created app/ package with 6 modules:
  - __init__.py: Application factory (create_app function)
  - routes.py: All route handlers (473 lines)
  - models.py: Data persistence layer (41 lines)
  - validators.py: Input validation functions (48 lines)
  - email_service.py: Email sending functions (85 lines)
  - utils.py: Utility functions (30 lines)

- Implemented Flask application factory pattern
- Created run.py as minimal entry point
- Updated all test imports to use new module structure
- Fixed template/static folder paths for package structure

- All 102 tests passing
- Improved maintainability with clear separation of concerns
- Follows Flask best practices for scalable applications
This commit is contained in:
2025-12-27 22:36:05 +01:00
parent 733ee756c7
commit f1d8bae6a1
18 changed files with 1254 additions and 229 deletions
+101 -92
View File
@@ -5,7 +5,7 @@ import pytest
import os
import yaml
from pathlib import Path
from app import (
from app.models import (
save_application_data,
load_application_data,
get_application_path,
@@ -19,84 +19,89 @@ class TestYAMLStorage:
def test_save_application_data(self, app, sample_application_data):
"""Test saving application data to YAML file."""
session_id = sample_application_data['session_id']
save_application_data(session_id, sample_application_data)
with app.app_context():
session_id = sample_application_data['session_id']
save_application_data(session_id, sample_application_data)
# Verify file was created
data_file = get_data_file_path(session_id)
assert os.path.exists(data_file)
# Verify file was created
data_file = get_data_file_path(session_id)
assert os.path.exists(data_file)
# Verify content
with open(data_file, 'r', encoding='utf-8') as f:
loaded_data = yaml.safe_load(f)
# Verify content
with open(data_file, 'r', encoding='utf-8') as f:
loaded_data = yaml.safe_load(f)
assert loaded_data['session_id'] == session_id
assert loaded_data['email'] == sample_application_data['email']
assert loaded_data['job_name'] == sample_application_data['job_name']
assert loaded_data['session_id'] == session_id
assert loaded_data['email'] == sample_application_data['email']
assert loaded_data['job_name'] == sample_application_data['job_name']
def test_load_application_data(self, app, sample_application_data):
"""Test loading application data from YAML file."""
session_id = sample_application_data['session_id']
with app.app_context():
session_id = sample_application_data['session_id']
# Save data first
save_application_data(session_id, sample_application_data)
# Save data first
save_application_data(session_id, sample_application_data)
# Load and verify
loaded_data = load_application_data(session_id)
assert loaded_data is not None
assert loaded_data['session_id'] == session_id
assert loaded_data['email'] == sample_application_data['email']
assert loaded_data['personal_info']['name'] == 'Müller'
# Load and verify
loaded_data = load_application_data(session_id)
assert loaded_data is not None
assert loaded_data['session_id'] == session_id
assert loaded_data['email'] == sample_application_data['email']
assert loaded_data['personal_info']['name'] == 'Müller'
def test_load_nonexistent_application(self, app):
"""Test loading data for non-existent application."""
loaded_data = load_application_data('nonexistent-session')
assert loaded_data is None
with app.app_context():
loaded_data = load_application_data('nonexistent-session')
assert loaded_data is None
def test_unicode_characters_in_yaml(self, app):
"""Test that German umlauts and special characters are preserved."""
session_id = 'test-unicode-123'
data = {
'session_id': session_id,
'email': 'test@example.com',
'job_name': 'Test Job',
'current_page': 2,
'personal_info': {
'name': 'Müller',
'firstname': 'Jürgen',
'city': 'München',
'notes': 'Grüße aus Zürich'
with app.app_context():
session_id = 'test-unicode-123'
data = {
'session_id': session_id,
'email': 'test@example.com',
'job_name': 'Test Job',
'current_page': 2,
'personal_info': {
'name': 'Müller',
'firstname': 'Jürgen',
'city': 'München',
'notes': 'Grüße aus Zürich'
}
}
}
save_application_data(session_id, data)
loaded_data = load_application_data(session_id)
save_application_data(session_id, data)
loaded_data = load_application_data(session_id)
assert loaded_data['personal_info']['name'] == 'Müller'
assert loaded_data['personal_info']['firstname'] == 'Jürgen'
assert loaded_data['personal_info']['city'] == 'München'
assert loaded_data['personal_info']['notes'] == 'Grüße aus Zürich'
assert loaded_data['personal_info']['name'] == 'Müller'
assert loaded_data['personal_info']['firstname'] == 'Jürgen'
assert loaded_data['personal_info']['city'] == 'München'
assert loaded_data['personal_info']['notes'] == 'Grüße aus Zürich'
def test_update_existing_application(self, app, sample_application_data):
"""Test updating existing application data."""
session_id = sample_application_data['session_id']
with app.app_context():
session_id = sample_application_data['session_id']
# Save initial data
save_application_data(session_id, sample_application_data)
# Save initial data
save_application_data(session_id, sample_application_data)
# Update data
sample_application_data['current_page'] = 3
sample_application_data['motivation_answers'] = {
'current_job': 'Developer',
'motivation': 'Great company'
}
save_application_data(session_id, sample_application_data)
# Update data
sample_application_data['current_page'] = 3
sample_application_data['motivation_answers'] = {
'current_job': 'Developer',
'motivation': 'Great company'
}
save_application_data(session_id, sample_application_data)
# Verify update
loaded_data = load_application_data(session_id)
assert loaded_data['current_page'] == 3
assert 'motivation_answers' in loaded_data
assert loaded_data['motivation_answers']['current_job'] == 'Developer'
# Verify update
loaded_data = load_application_data(session_id)
assert loaded_data['current_page'] == 3
assert 'motivation_answers' in loaded_data
assert loaded_data['motivation_answers']['current_job'] == 'Developer'
class TestFolderStructure:
@@ -104,30 +109,33 @@ class TestFolderStructure:
def test_application_folder_created(self, app, sample_application_data):
"""Test that application folder is created when saving data."""
session_id = sample_application_data['session_id']
save_application_data(session_id, sample_application_data)
with app.app_context():
session_id = sample_application_data['session_id']
save_application_data(session_id, sample_application_data)
app_path = get_application_path(session_id)
assert os.path.exists(app_path)
assert os.path.isdir(app_path)
app_path = get_application_path(session_id)
assert os.path.exists(app_path)
assert os.path.isdir(app_path)
def test_data_yaml_created(self, app, sample_application_data):
"""Test that data.yaml file is created."""
session_id = sample_application_data['session_id']
save_application_data(session_id, sample_application_data)
with app.app_context():
session_id = sample_application_data['session_id']
save_application_data(session_id, sample_application_data)
data_file = get_data_file_path(session_id)
assert os.path.exists(data_file)
assert os.path.isfile(data_file)
assert data_file.endswith('data.yaml')
data_file = get_data_file_path(session_id)
assert os.path.exists(data_file)
assert os.path.isfile(data_file)
assert data_file.endswith('data.yaml')
def test_attachments_folder_path(self, app):
"""Test that attachments folder path is correct."""
session_id = 'test-attachments-123'
attachments_path = get_attachments_path(session_id)
with app.app_context():
session_id = 'test-attachments-123'
attachments_path = get_attachments_path(session_id)
assert 'attachments' in attachments_path
assert session_id in attachments_path
assert 'attachments' in attachments_path
assert session_id in attachments_path
class TestDataPersistence:
@@ -143,7 +151,7 @@ class TestDataPersistence:
# Extract session ID from response
# For testing, we'll create a known session
from app import save_application_data
from app.models import save_application_data
session_id = 'test-persist-123'
save_application_data(session_id, {
'session_id': session_id,
@@ -189,26 +197,27 @@ class TestDataPersistence:
def test_empty_optional_fields_preserved(self, app):
"""Test that empty optional fields are preserved correctly."""
session_id = 'test-empty-fields'
data = {
'session_id': session_id,
'email': 'test@example.com',
'job_name': 'Test',
'current_page': 3,
'personal_info': {
'civil_status': '' # Optional, empty
},
'motivation_answers': {
'current_job': '', # Optional, empty
'motivation': 'Some text',
'qualifications': '',
'salary': ''
with app.app_context():
session_id = 'test-empty-fields'
data = {
'session_id': session_id,
'email': 'test@example.com',
'job_name': 'Test',
'current_page': 3,
'personal_info': {
'civil_status': '' # Optional, empty
},
'motivation_answers': {
'current_job': '', # Optional, empty
'motivation': 'Some text',
'qualifications': '',
'salary': ''
}
}
}
save_application_data(session_id, data)
loaded_data = load_application_data(session_id)
save_application_data(session_id, data)
loaded_data = load_application_data(session_id)
assert loaded_data['personal_info']['civil_status'] == ''
assert loaded_data['motivation_answers']['current_job'] == ''
assert loaded_data['motivation_answers']['motivation'] == 'Some text'
assert loaded_data['personal_info']['civil_status'] == ''
assert loaded_data['motivation_answers']['current_job'] == ''
assert loaded_data['motivation_answers']['motivation'] == 'Some text'