feat: enhance child edit and view components with improved form handling and validation
All checks were successful
Chore App Build and Push Docker Images / build-and-push (push) Successful in 1m4s

- Added `requireDirty` prop to `EntityEditForm` for dirty state management.
- Updated `ChildEditView` to handle initial data loading and image selection more robustly.
- Refactored `ChildView` to remove unused reward dialog logic and prevent API calls in child mode.
- Improved type definitions for form fields and initial data in `ChildEditView`.
- Enhanced error handling in form submissions across components.
- Implemented cross-tab logout synchronization on password reset in the auth store.
- Added tests for login and entity edit form functionalities to ensure proper behavior.
- Introduced global fetch interceptor for handling unauthorized responses.
- Documented password reset flow and its implications on session management.
This commit is contained in:
2026-02-17 17:18:03 -05:00
parent 5e22e5e0ee
commit 31ea76f013
29 changed files with 1000 additions and 164 deletions

View File

@@ -100,6 +100,38 @@ def test_reset_password_hashes_new_password(client):
assert user_dict['password'].startswith('scrypt:')
assert check_password_hash(user_dict['password'], 'newpassword123')
def test_reset_password_invalidates_existing_jwt(client):
users_db.remove(Query().email == 'test@example.com')
user = User(
first_name='Test',
last_name='User',
email='test@example.com',
password=generate_password_hash('oldpassword123'),
verified=True,
reset_token='validtoken2',
reset_token_created=datetime.utcnow().isoformat(),
)
users_db.insert(user.to_dict())
login_response = client.post('/auth/login', json={'email': 'test@example.com', 'password': 'oldpassword123'})
assert login_response.status_code == 200
login_cookie = login_response.headers.get('Set-Cookie', '')
assert 'token=' in login_cookie
old_token = login_cookie.split('token=', 1)[1].split(';', 1)[0]
assert old_token
reset_response = client.post('/auth/reset-password', json={'token': 'validtoken2', 'password': 'newpassword123'})
assert reset_response.status_code == 200
reset_cookie = reset_response.headers.get('Set-Cookie', '')
assert 'token=' in reset_cookie
# Set the old token as a cookie and test that it's now invalid
client.set_cookie('token', old_token)
me_response = client.get('/auth/me')
assert me_response.status_code == 401
assert me_response.json['code'] == 'INVALID_TOKEN'
def test_migration_script_hashes_plain_text_passwords():
"""Test the migration script hashes plain text passwords."""
# Clean up

View File

@@ -80,6 +80,36 @@ def test_list_tasks(client):
assert len(data['tasks']) == 2
def test_list_tasks_sorted_by_is_good_then_user_then_default_then_name(client):
task_db.truncate()
task_db.insert({'id': 'u_good_z', 'name': 'Zoo', 'points': 1, 'is_good': True, 'user_id': 'testuserid'})
task_db.insert({'id': 'u_good_a', 'name': 'Apple', 'points': 1, 'is_good': True, 'user_id': 'testuserid'})
task_db.insert({'id': 'd_good_m', 'name': 'Mop', 'points': 1, 'is_good': True, 'user_id': None})
task_db.insert({'id': 'd_good_b', 'name': 'Brush', 'points': 1, 'is_good': True, 'user_id': None})
task_db.insert({'id': 'u_bad_c', 'name': 'Chore', 'points': 1, 'is_good': False, 'user_id': 'testuserid'})
task_db.insert({'id': 'u_bad_a', 'name': 'Alarm', 'points': 1, 'is_good': False, 'user_id': 'testuserid'})
task_db.insert({'id': 'd_bad_y', 'name': 'Yell', 'points': 1, 'is_good': False, 'user_id': None})
task_db.insert({'id': 'd_bad_b', 'name': 'Bicker', 'points': 1, 'is_good': False, 'user_id': None})
response = client.get('/task/list')
assert response.status_code == 200
tasks = response.json['tasks']
ordered_ids = [t['id'] for t in tasks]
assert ordered_ids == [
'u_good_a',
'u_good_z',
'd_good_b',
'd_good_m',
'u_bad_a',
'u_bad_c',
'd_bad_b',
'd_bad_y',
]
def test_get_task_not_found(client):
response = client.get('/task/nonexistent-id')
assert response.status_code == 404