feat: add functions to validate today's timestamps and update pending status logic
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m52s

This commit is contained in:
2026-05-03 11:53:21 -04:00
parent 75d3d6dc39
commit ce3d1b3d54
3 changed files with 248 additions and 9 deletions

View File

@@ -10,7 +10,7 @@ from tinydb import Query
from models.child import Child
import jwt
from werkzeug.security import generate_password_hash
from datetime import date as date_type
from datetime import date as date_type, datetime, timedelta, timezone
# Test user credentials
@@ -382,6 +382,7 @@ def _setup_sched_child_and_tasks(task_db, child_db):
})
chore_schedules_db.remove(Query().child_id == CHILD_SCHED_ID)
task_extensions_db.remove(Query().child_id == CHILD_SCHED_ID)
pending_confirmations_db.remove(Query().child_id == CHILD_SCHED_ID)
def test_list_child_tasks_always_has_schedule_and_extension_date_keys(client):
@@ -517,6 +518,113 @@ def test_list_child_tasks_no_server_side_filtering(client):
assert extra_id in returned_ids
def test_list_child_tasks_shows_pending_for_today(client):
"""A chore confirmed today should return pending_status='pending'."""
_setup_sched_child_and_tasks(task_db, child_db)
now_ts = datetime.now(timezone.utc).timestamp()
pending_confirmations_db.insert({
'id': 'pend_today_chore',
'child_id': CHILD_SCHED_ID,
'entity_id': TASK_GOOD_ID,
'entity_type': 'chore',
'user_id': 'testuserid',
'status': 'pending',
'approved_at': None,
'created_at': now_ts,
'updated_at': now_ts,
})
resp = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
assert resp.status_code == 200
tasks = {t['id']: t for t in resp.get_json()['tasks']}
assert tasks[TASK_GOOD_ID]['pending_status'] == 'pending'
assert tasks[TASK_GOOD_ID]['approved_at'] is None
def test_list_child_tasks_clears_stale_approved_and_pending(client):
"""Yesterday's chore pending/approved records should be reset and ignored."""
_setup_sched_child_and_tasks(task_db, child_db)
old_ts = (datetime.now(timezone.utc) - timedelta(days=2)).timestamp()
old_approved = (datetime.now(timezone.utc) - timedelta(days=2)).isoformat()
pending_confirmations_db.insert({
'id': 'pend_old_chore_pending',
'child_id': CHILD_SCHED_ID,
'entity_id': TASK_GOOD_ID,
'entity_type': 'chore',
'user_id': 'testuserid',
'status': 'pending',
'approved_at': None,
'created_at': old_ts,
'updated_at': old_ts,
})
resp = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
assert resp.status_code == 200
tasks = {t['id']: t for t in resp.get_json()['tasks']}
assert tasks[TASK_GOOD_ID]['pending_status'] is None
assert tasks[TASK_GOOD_ID]['approved_at'] is None
# Reinsert as stale approved and ensure it is also cleared.
pending_confirmations_db.insert({
'id': 'pend_old_chore_approved',
'child_id': CHILD_SCHED_ID,
'entity_id': TASK_GOOD_ID,
'entity_type': 'chore',
'user_id': 'testuserid',
'status': 'approved',
'approved_at': old_approved,
'created_at': old_ts,
'updated_at': old_ts,
})
resp2 = client.get(f'/child/{CHILD_SCHED_ID}/list-tasks')
assert resp2.status_code == 200
tasks2 = {t['id']: t for t in resp2.get_json()['tasks']}
assert tasks2[TASK_GOOD_ID]['pending_status'] is None
assert tasks2[TASK_GOOD_ID]['approved_at'] is None
def test_confirm_chore_allows_when_previous_pending_is_stale(client):
"""A stale pending chore record from a prior day must not block confirm-chore."""
old_ts = (datetime.now(timezone.utc) - timedelta(days=2)).timestamp()
task_db.insert({'id': 't_stale_chore', 'name': 'Stale Chore', 'points': 2, 'type': 'chore', 'user_id': 'testuserid'})
child_db.insert({
'id': 'child_stale_chore',
'name': 'Stale Chore Kid',
'age': 8,
'points': 0,
'tasks': ['t_stale_chore'],
'rewards': [],
'user_id': 'testuserid',
})
pending_confirmations_db.insert({
'id': 'pend_stale_chore',
'child_id': 'child_stale_chore',
'entity_id': 't_stale_chore',
'entity_type': 'chore',
'user_id': 'testuserid',
'status': 'pending',
'approved_at': None,
'created_at': old_ts,
'updated_at': old_ts,
})
resp = client.post('/child/child_stale_chore/confirm-chore', json={'task_id': 't_stale_chore'})
assert resp.status_code == 200
active_pending = pending_confirmations_db.search(
(Query().child_id == 'child_stale_chore') & (Query().entity_id == 't_stale_chore') &
(Query().entity_type == 'chore') & (Query().status == 'pending')
)
assert len(active_pending) == 1
assert active_pending[0].get('id') != 'pend_stale_chore'
pending_confirmations_db.remove(Query().child_id == 'child_stale_chore')
child_db.remove(Query().id == 'child_stale_chore')
task_db.remove(Query().id == 't_stale_chore')
# ---------------------------------------------------------------------------
# request-reward: duplicate guard
# ---------------------------------------------------------------------------
@@ -547,6 +655,82 @@ def test_request_reward_duplicate_returns_409(client):
reward_db.remove(Query().id == 'r_dup')
def test_request_reward_allows_new_when_stale_pending_exists(client):
"""A stale pending reward from a prior day must not block a new request."""
old_ts = (datetime.now(timezone.utc) - timedelta(days=2)).timestamp()
reward_db.insert({'id': 'r_stale', 'name': 'Stale Reward', 'cost': 5, 'user_id': 'testuserid'})
child_db.insert({
'id': 'child_stale',
'name': 'Stale Kid',
'age': 8,
'points': 20,
'tasks': [],
'rewards': ['r_stale'],
'user_id': 'testuserid',
})
pending_confirmations_db.insert({
'id': 'pend_stale_reward',
'child_id': 'child_stale',
'entity_id': 'r_stale',
'entity_type': 'reward',
'user_id': 'testuserid',
'status': 'pending',
'approved_at': None,
'created_at': old_ts,
'updated_at': old_ts,
})
resp = client.post('/child/child_stale/request-reward', json={'reward_id': 'r_stale'})
assert resp.status_code == 200
active_pending = pending_confirmations_db.search(
(Query().child_id == 'child_stale') & (Query().entity_id == 'r_stale') &
(Query().entity_type == 'reward') & (Query().status == 'pending')
)
assert len(active_pending) == 1
assert active_pending[0].get('id') != 'pend_stale_reward'
pending_confirmations_db.remove(Query().child_id == 'child_stale')
child_db.remove(Query().id == 'child_stale')
reward_db.remove(Query().id == 'r_stale')
def test_reward_status_ignores_stale_pending_reward(client):
"""reward-status should not mark a reward as redeeming if pending is stale."""
old_ts = (datetime.now(timezone.utc) - timedelta(days=2)).timestamp()
reward_db.insert({'id': 'r_status_stale', 'name': 'Status Reward', 'cost': 4, 'user_id': 'testuserid'})
child_db.insert({
'id': 'child_status_stale',
'name': 'Status Kid',
'age': 9,
'points': 10,
'tasks': [],
'rewards': ['r_status_stale'],
'user_id': 'testuserid',
})
pending_confirmations_db.insert({
'id': 'pend_status_stale',
'child_id': 'child_status_stale',
'entity_id': 'r_status_stale',
'entity_type': 'reward',
'user_id': 'testuserid',
'status': 'pending',
'approved_at': None,
'created_at': old_ts,
'updated_at': old_ts,
})
resp = client.get('/child/child_status_stale/reward-status')
assert resp.status_code == 200
statuses = {s['id']: s for s in resp.get_json()['reward_status']}
assert statuses['r_status_stale']['redeeming'] is False
assert pending_confirmations_db.get(Query().id == 'pend_status_stale') is None
pending_confirmations_db.remove(Query().child_id == 'child_status_stale')
child_db.remove(Query().id == 'child_status_stale')
reward_db.remove(Query().id == 'r_status_stale')
# ---------------------------------------------------------------------------
# deny-reward-request endpoint
# ---------------------------------------------------------------------------