feat: Implement routines feature with CRUD operations and child assignment
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m0s
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m0s
- Add backend routines management with add, get, update, delete, and list functionalities. - Create models for Routine, RoutineItem, RoutineSchedule, and RoutineExtension. - Develop event types for routine confirmation and modification. - Implement frontend components for routine assignment, confirmation dialog, and routine management views. - Add unit tests for routine API and integration tests for routine CRUD flow. - Create end-to-end test plan for routines feature covering parent and child interactions.
This commit is contained in:
187
backend/tests/test_routine_feature_api.py
Normal file
187
backend/tests/test_routine_feature_api.py
Normal file
@@ -0,0 +1,187 @@
|
||||
from flask import Flask
|
||||
from tinydb import Query
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
from tests.conftest import TEST_SECRET_KEY, TEST_REFRESH_TOKEN_EXPIRY_DAYS
|
||||
from api.auth_api import auth_api
|
||||
from api.routine_api import routine_api
|
||||
from api.routine_item_api import routine_item_api
|
||||
from api.child_routine_api import child_routine_api
|
||||
from api.routine_schedule_api import routine_schedule_api
|
||||
from db.db import (
|
||||
users_db,
|
||||
child_db,
|
||||
routine_db,
|
||||
routine_items_db,
|
||||
routine_schedules_db,
|
||||
routine_extensions_db,
|
||||
pending_confirmations_db,
|
||||
)
|
||||
|
||||
|
||||
TEST_USER_ID = 'routine-user-1'
|
||||
TEST_EMAIL = 'routine-user@example.com'
|
||||
TEST_PASSWORD = 'testpass'
|
||||
|
||||
|
||||
def add_test_user():
|
||||
users_db.remove(Query().email == TEST_EMAIL)
|
||||
users_db.insert({
|
||||
'id': TEST_USER_ID,
|
||||
'first_name': 'Routine',
|
||||
'last_name': 'Tester',
|
||||
'email': TEST_EMAIL,
|
||||
'password': generate_password_hash(TEST_PASSWORD),
|
||||
'verified': True,
|
||||
'image_id': 'boy01',
|
||||
})
|
||||
|
||||
|
||||
def login_and_set_cookie(client):
|
||||
resp = client.post('/auth/login', json={'email': TEST_EMAIL, 'password': TEST_PASSWORD})
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
def seed_child(child_id: str):
|
||||
child_db.remove(Query().id == child_id)
|
||||
child_db.insert({
|
||||
'id': child_id,
|
||||
'name': 'Routine Kid',
|
||||
'age': 9,
|
||||
'tasks': [],
|
||||
'routines': [],
|
||||
'rewards': [],
|
||||
'points': 0,
|
||||
'image_id': 'boy01',
|
||||
'user_id': TEST_USER_ID,
|
||||
})
|
||||
|
||||
|
||||
def _first_routine_id():
|
||||
routines = routine_db.all()
|
||||
assert routines
|
||||
return routines[0]['id']
|
||||
|
||||
|
||||
def _first_confirmation_id():
|
||||
confirmations = pending_confirmations_db.all()
|
||||
assert confirmations
|
||||
return confirmations[0]['id']
|
||||
|
||||
|
||||
def _make_client():
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(auth_api, url_prefix='/auth')
|
||||
app.register_blueprint(routine_api)
|
||||
app.register_blueprint(routine_item_api)
|
||||
app.register_blueprint(child_routine_api)
|
||||
app.register_blueprint(routine_schedule_api)
|
||||
app.config['TESTING'] = True
|
||||
app.config['SECRET_KEY'] = TEST_SECRET_KEY
|
||||
app.config['REFRESH_TOKEN_EXPIRY_DAYS'] = TEST_REFRESH_TOKEN_EXPIRY_DAYS
|
||||
return app
|
||||
|
||||
|
||||
def setup_function(_):
|
||||
routine_db.truncate()
|
||||
routine_items_db.truncate()
|
||||
routine_schedules_db.truncate()
|
||||
routine_extensions_db.truncate()
|
||||
pending_confirmations_db.truncate()
|
||||
child_db.truncate()
|
||||
|
||||
|
||||
def test_routine_crud_flow():
|
||||
app = _make_client()
|
||||
with app.test_client() as client:
|
||||
add_test_user()
|
||||
login_and_set_cookie(client)
|
||||
|
||||
add_resp = client.put('/routine/add', json={'name': 'Morning Routine', 'points': 8, 'image_id': 'sun'})
|
||||
assert add_resp.status_code == 201
|
||||
|
||||
rid = _first_routine_id()
|
||||
|
||||
list_resp = client.get('/routine/list')
|
||||
assert list_resp.status_code == 200
|
||||
routines = list_resp.get_json()['routines']
|
||||
assert len(routines) == 1
|
||||
assert routines[0]['name'] == 'Morning Routine'
|
||||
|
||||
edit_resp = client.put(f'/routine/{rid}/edit', json={'points': 10})
|
||||
assert edit_resp.status_code == 200
|
||||
assert edit_resp.get_json()['points'] == 10
|
||||
|
||||
delete_resp = client.delete(f'/routine/{rid}')
|
||||
assert delete_resp.status_code == 200
|
||||
assert routine_db.all() == []
|
||||
|
||||
|
||||
def test_child_routine_assignment_confirmation_and_approval():
|
||||
app = _make_client()
|
||||
with app.test_client() as client:
|
||||
add_test_user()
|
||||
login_and_set_cookie(client)
|
||||
|
||||
seed_child('routine-child-1')
|
||||
client.put('/routine/add', json={'name': 'Evening Routine', 'points': 6, 'image_id': 'moon'})
|
||||
rid = _first_routine_id()
|
||||
|
||||
add_item_resp = client.put(f'/routine/{rid}/item/add', json={'name': 'Brush Teeth', 'order': 0})
|
||||
assert add_item_resp.status_code == 201
|
||||
|
||||
assign_resp = client.post('/child/routine-child-1/assign-routine', json={'routine_id': rid})
|
||||
assert assign_resp.status_code == 200
|
||||
|
||||
list_resp = client.get('/child/routine-child-1/list-routines')
|
||||
assert list_resp.status_code == 200
|
||||
routines = list_resp.get_json()['routines']
|
||||
assert len(routines) == 1
|
||||
assert routines[0]['id'] == rid
|
||||
assert routines[0]['items'][0]['name'] == 'Brush Teeth'
|
||||
|
||||
confirm_resp = client.post('/child/routine-child-1/confirm-routine', json={'routine_id': rid})
|
||||
assert confirm_resp.status_code == 200
|
||||
|
||||
confirmation_id = _first_confirmation_id()
|
||||
approve_resp = client.post(f'/child/routine-child-1/approve-routine/{confirmation_id}')
|
||||
assert approve_resp.status_code == 200
|
||||
|
||||
child = child_db.get(Query().id == 'routine-child-1')
|
||||
assert child['points'] == 6
|
||||
|
||||
|
||||
def test_routine_schedule_and_extend_flow():
|
||||
app = _make_client()
|
||||
with app.test_client() as client:
|
||||
add_test_user()
|
||||
login_and_set_cookie(client)
|
||||
|
||||
seed_child('routine-child-2')
|
||||
client.put('/routine/add', json={'name': 'School Routine', 'points': 5, 'image_id': 'book'})
|
||||
rid = _first_routine_id()
|
||||
|
||||
client.post('/child/routine-child-2/assign-routine', json={'routine_id': rid})
|
||||
|
||||
set_resp = client.put(
|
||||
f'/child/routine-child-2/routine/{rid}/schedule',
|
||||
json={
|
||||
'mode': 'days',
|
||||
'day_configs': [{'day': 1, 'hour': 8, 'minute': 0}],
|
||||
'default_hour': 8,
|
||||
'default_minute': 0,
|
||||
'default_has_deadline': True,
|
||||
},
|
||||
)
|
||||
assert set_resp.status_code == 200
|
||||
|
||||
get_resp = client.get(f'/child/routine-child-2/routine/{rid}/schedule')
|
||||
assert get_resp.status_code == 200
|
||||
assert get_resp.get_json()['mode'] == 'days'
|
||||
|
||||
extend_resp = client.post(
|
||||
f'/child/routine-child-2/routine/{rid}/extend',
|
||||
json={'date': '2026-05-04'},
|
||||
)
|
||||
assert extend_resp.status_code == 200
|
||||
assert extend_resp.get_json()['routine_id'] == rid
|
||||
Reference in New Issue
Block a user