118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
from datetime import date
|
|
|
|
from app import db as d
|
|
from app.config import get_settings
|
|
from app.services import compute_service_due
|
|
|
|
|
|
def _conn():
|
|
settings = get_settings()
|
|
settings.data_dir.mkdir(parents=True, exist_ok=True)
|
|
d.init_schema(settings.resolved_db_path)
|
|
return d.connect(settings.resolved_db_path)
|
|
|
|
|
|
def _mk_bike(conn, interval):
|
|
return d.create_bike(conn, "B", interval)
|
|
|
|
|
|
def _event(conn, bike_id, type, day, mileage, cost=None):
|
|
d.create_event(conn, bike_id, type, day, mileage, cost, "n")
|
|
|
|
|
|
def test_no_data_when_no_service():
|
|
conn = _conn()
|
|
bike_id = _mk_bike(conn, 5000)
|
|
_event(conn, bike_id, "tyre", date(2026, 1, 1), 10000)
|
|
due = compute_service_due(conn, d.get_bike(conn, bike_id))
|
|
assert due.status == "no-data"
|
|
conn.close()
|
|
|
|
|
|
def test_ok_when_under_interval():
|
|
conn = _conn()
|
|
bike_id = _mk_bike(conn, 5000)
|
|
_event(conn, bike_id, "service", date(2026, 1, 1), 10000)
|
|
_event(conn, bike_id, "tyre", date(2026, 6, 1), 12000)
|
|
due = compute_service_due(conn, d.get_bike(conn, bike_id))
|
|
assert due.status == "ok"
|
|
assert due.miles_since == 2000
|
|
assert due.miles_until_due == 3000
|
|
conn.close()
|
|
|
|
|
|
def test_overdue_when_over_interval():
|
|
conn = _conn()
|
|
bike_id = _mk_bike(conn, 5000)
|
|
_event(conn, bike_id, "service", date(2026, 1, 1), 10000)
|
|
_event(conn, bike_id, "other", date(2026, 6, 1), 16000)
|
|
due = compute_service_due(conn, d.get_bike(conn, bike_id))
|
|
assert due.status == "overdue"
|
|
assert due.miles_since == 6000
|
|
assert due.miles_until_due == -1000
|
|
conn.close()
|
|
|
|
|
|
def test_exactly_at_interval_is_due_soon():
|
|
conn = _conn()
|
|
bike_id = _mk_bike(conn, 5000)
|
|
_event(conn, bike_id, "service", date(2026, 1, 1), 10000)
|
|
_event(conn, bike_id, "other", date(2026, 6, 1), 15000)
|
|
due = compute_service_due(conn, d.get_bike(conn, bike_id))
|
|
assert due.status == "due-soon"
|
|
assert due.miles_until_due == 0
|
|
conn.close()
|
|
|
|
|
|
def test_within_lead_time_is_due_soon():
|
|
conn = _conn()
|
|
bike_id = _mk_bike(conn, 5000)
|
|
_event(conn, bike_id, "service", date(2026, 1, 1), 10000)
|
|
_event(conn, bike_id, "other", date(2026, 6, 1), 14900) # 100 mi left
|
|
due = compute_service_due(conn, d.get_bike(conn, bike_id))
|
|
assert due.status == "due-soon"
|
|
assert due.miles_until_due == 100
|
|
conn.close()
|
|
|
|
|
|
def test_just_outside_lead_time_is_ok():
|
|
conn = _conn()
|
|
bike_id = _mk_bike(conn, 5000)
|
|
_event(conn, bike_id, "service", date(2026, 1, 1), 10000)
|
|
_event(conn, bike_id, "other", date(2026, 6, 1), 14750) # 250 mi left
|
|
due = compute_service_due(conn, d.get_bike(conn, bike_id))
|
|
assert due.status == "ok"
|
|
assert due.miles_until_due == 250
|
|
conn.close()
|
|
|
|
|
|
def test_no_interval_set_is_no_data():
|
|
conn = _conn()
|
|
bike_id = _mk_bike(conn, None)
|
|
_event(conn, bike_id, "service", date(2026, 1, 1), 10000)
|
|
due = compute_service_due(conn, d.get_bike(conn, bike_id))
|
|
assert due.status == "no-data"
|
|
conn.close()
|
|
|
|
|
|
def test_latest_service_used_not_earliest():
|
|
conn = _conn()
|
|
bike_id = _mk_bike(conn, 5000)
|
|
_event(conn, bike_id, "service", date(2025, 1, 1), 8000)
|
|
_event(conn, bike_id, "service", date(2026, 1, 1), 10000) # most recent
|
|
_event(conn, bike_id, "other", date(2026, 6, 1), 14000)
|
|
due = compute_service_due(conn, d.get_bike(conn, bike_id))
|
|
assert due.status == "ok"
|
|
assert due.miles_since == 4000
|
|
conn.close()
|
|
|
|
|
|
def test_service_without_mileage_is_ignored():
|
|
conn = _conn()
|
|
bike_id = _mk_bike(conn, 5000)
|
|
_event(conn, bike_id, "service", date(2026, 1, 1), None) # no mileage
|
|
due = compute_service_due(conn, d.get_bike(conn, bike_id))
|
|
assert due.status == "no-data"
|
|
assert due.last_service is None
|
|
conn.close()
|