Completed and abandoned habits were both lumped under a single terminal state, making it impossible to tell "finished successfully" apart from "gave up" when reviewing habit history.
158 lines
6.4 KiB
Python
158 lines
6.4 KiB
Python
from datetime import date, time
|
|
|
|
from app.models.habit import ALL_WEEKDAYS_MASK, Habit, HabitStatus, HabitType
|
|
from app.models.habit_log import HabitLog
|
|
from app.schemas.habit import HabitCreate, HabitUpdate
|
|
from app.services import habit_service
|
|
|
|
|
|
def _make_habit(db_session, user_id, name="아침 일찍 일어나기", **overrides):
|
|
data = HabitCreate(
|
|
name=name,
|
|
habit_type=overrides.pop("habit_type", HabitType.BUILD),
|
|
weekdays_mask=overrides.pop("weekdays_mask", ALL_WEEKDAYS_MASK),
|
|
condition_text=overrides.pop("condition_text", None),
|
|
reminder_time=overrides.pop("reminder_time", None),
|
|
)
|
|
return habit_service.create_habit(db_session, user_id, data)
|
|
|
|
|
|
def test_create_habit_defaults_to_active(db_session, test_user):
|
|
habit = _make_habit(db_session, test_user.id)
|
|
assert habit.status == HabitStatus.ACTIVE
|
|
assert habit.user_id == test_user.id
|
|
assert habit.id is not None
|
|
|
|
|
|
def test_list_habits_scoped_to_user(db_session, test_user, other_user):
|
|
_make_habit(db_session, test_user.id, name="내 습관")
|
|
_make_habit(db_session, other_user.id, name="남의 습관")
|
|
|
|
mine = habit_service.list_habits(db_session, test_user.id)
|
|
assert [h.name for h in mine] == ["내 습관"]
|
|
|
|
|
|
def test_get_habit_returns_none_for_other_users_habit(db_session, test_user, other_user):
|
|
other_habit = _make_habit(db_session, other_user.id, name="남의 습관")
|
|
|
|
assert habit_service.get_habit(db_session, other_habit.id, test_user.id) is None
|
|
assert habit_service.get_habit(db_session, other_habit.id, other_user.id) is not None
|
|
|
|
|
|
def test_list_habits_filters_by_type_and_status(db_session, test_user):
|
|
build = _make_habit(db_session, test_user.id, name="빌드", habit_type=HabitType.BUILD)
|
|
quit_ = _make_habit(db_session, test_user.id, name="퀴트", habit_type=HabitType.QUIT)
|
|
habit_service.complete_habit(db_session, quit_)
|
|
|
|
active_builds = habit_service.list_habits(db_session, test_user.id, habit_type=HabitType.BUILD)
|
|
assert [h.id for h in active_builds] == [build.id]
|
|
|
|
completed = habit_service.list_habits(db_session, test_user.id, status=HabitStatus.COMPLETED)
|
|
assert [h.id for h in completed] == [quit_.id]
|
|
|
|
|
|
def test_update_habit_overwrites_fields(db_session, test_user):
|
|
habit = _make_habit(db_session, test_user.id, name="원래 이름")
|
|
updated = habit_service.update_habit(
|
|
db_session,
|
|
habit,
|
|
HabitUpdate(name="바뀐 이름", habit_type=HabitType.BUILD, weekdays_mask=0b0000001, condition_text=" "),
|
|
)
|
|
assert updated.name == "바뀐 이름"
|
|
assert updated.weekdays_mask == 0b0000001
|
|
assert updated.condition_text is None # blank_condition_to_none 검증기 통과 확인
|
|
|
|
|
|
def test_delete_habit_removes_row_and_cascades_logs(db_session, test_user):
|
|
habit = _make_habit(db_session, test_user.id)
|
|
db_session.add(HabitLog(habit_id=habit.id, log_date=date.today()))
|
|
db_session.commit()
|
|
|
|
habit_service.delete_habit(db_session, habit)
|
|
|
|
assert db_session.get(Habit, habit.id) is None
|
|
assert db_session.query(HabitLog).filter_by(habit_id=habit.id).count() == 0
|
|
|
|
|
|
def test_complete_and_reactivate_habit(db_session, test_user):
|
|
habit = _make_habit(db_session, test_user.id)
|
|
|
|
completed = habit_service.complete_habit(db_session, habit)
|
|
assert completed.status == HabitStatus.COMPLETED
|
|
assert completed.completed_at is not None
|
|
|
|
reactivated = habit_service.reactivate_habit(db_session, habit)
|
|
assert reactivated.status == HabitStatus.ACTIVE
|
|
assert reactivated.completed_at is None
|
|
|
|
|
|
def test_abandon_and_reactivate_habit(db_session, test_user):
|
|
habit = _make_habit(db_session, test_user.id)
|
|
|
|
abandoned = habit_service.abandon_habit(db_session, habit)
|
|
assert abandoned.status == HabitStatus.ABANDONED
|
|
assert abandoned.abandoned_at is not None
|
|
|
|
reactivated = habit_service.reactivate_habit(db_session, habit)
|
|
assert reactivated.status == HabitStatus.ACTIVE
|
|
assert reactivated.abandoned_at is None
|
|
|
|
|
|
def test_list_habits_filters_by_abandoned_status(db_session, test_user):
|
|
build = _make_habit(db_session, test_user.id, name="빌드")
|
|
quit_ = _make_habit(db_session, test_user.id, name="퀴트", habit_type=HabitType.QUIT)
|
|
habit_service.abandon_habit(db_session, quit_)
|
|
|
|
active = habit_service.list_habits(db_session, test_user.id, status=HabitStatus.ACTIVE)
|
|
assert [h.id for h in active] == [build.id]
|
|
|
|
abandoned = habit_service.list_habits(db_session, test_user.id, status=HabitStatus.ABANDONED)
|
|
assert [h.id for h in abandoned] == [quit_.id]
|
|
|
|
|
|
def test_reorder_habits_applies_given_order(db_session, test_user):
|
|
a = _make_habit(db_session, test_user.id, name="A")
|
|
b = _make_habit(db_session, test_user.id, name="B")
|
|
c = _make_habit(db_session, test_user.id, name="C")
|
|
|
|
habit_service.reorder_habits(db_session, test_user.id, [c.id, a.id, b.id])
|
|
|
|
ordered = habit_service.list_habits(db_session, test_user.id)
|
|
assert [h.name for h in ordered] == ["C", "A", "B"]
|
|
|
|
|
|
def test_reorder_habits_ignores_other_users_ids(db_session, test_user, other_user):
|
|
mine = _make_habit(db_session, test_user.id, name="내 것")
|
|
others = _make_habit(db_session, other_user.id, name="남의 것")
|
|
|
|
# 남의 habit_id가 섞여 들어와도 그 습관의 sort_order는 바뀌지 않아야 한다 (IDOR 방지).
|
|
habit_service.reorder_habits(db_session, test_user.id, [others.id, mine.id])
|
|
|
|
assert others.sort_order is None
|
|
assert mine.sort_order is not None
|
|
|
|
|
|
def test_list_active_habits_with_reminders_is_not_user_scoped(db_session, test_user, other_user):
|
|
with_reminder = _make_habit(db_session, test_user.id, name="알림 있음", reminder_time=time(9, 0))
|
|
_make_habit(db_session, other_user.id, name="알림 없음")
|
|
|
|
result = habit_service.list_active_habits_with_reminders(db_session)
|
|
assert [h.id for h in result] == [with_reminder.id]
|
|
|
|
|
|
def test_list_active_user_ids_is_not_user_scoped(db_session, test_user, other_user):
|
|
_make_habit(db_session, test_user.id)
|
|
_make_habit(db_session, other_user.id)
|
|
|
|
result = habit_service.list_active_user_ids(db_session)
|
|
assert set(result) == {test_user.id, other_user.id}
|
|
|
|
|
|
def test_list_active_user_ids_excludes_completed_only_users(db_session, test_user, other_user):
|
|
_make_habit(db_session, test_user.id)
|
|
completed_only = _make_habit(db_session, other_user.id)
|
|
habit_service.complete_habit(db_session, completed_only)
|
|
|
|
result = habit_service.list_active_user_ids(db_session)
|
|
assert result == [test_user.id]
|