Paginate activity history and aggregate dashboard totals in SQL

This commit is contained in:
Peter Stockings
2026-10-02 14:36:35 +10:00
parent a300ea007d
commit 5830976e31
11 changed files with 386 additions and 65 deletions
+165 -1
View File
@@ -62,9 +62,173 @@ def app(tmp_path):
def login(client, name="runner"):
return client.post(
response = client.post(
"/login", data={"email": f"{name}@example.test", "password": "a-long-test-password"}
)
assert response.status_code == 302, response.data.decode()
return response
def add_history(count, visibility="private"):
from datetime import timedelta
for index in range(count):
db.session.add(
Activity(
owner_id=1,
source_key=f"history:{index}",
title=f"History run {index:03d}",
sport="running",
started_at=datetime(2026, 9, 1, tzinfo=UTC) + timedelta(days=index),
distance_m=1000,
duration_s=300,
elevation_m=1,
visibility=visibility,
efforts_version=EFFORTS_VERSION,
best_efforts=[
{
"label": "1 km",
"distance": 1000,
"seconds": 250 if index == 0 else 300,
"start_m": 0,
"end_m": 1000,
}
],
samples=[],
laps=[],
)
)
db.session.commit()
def test_history_pages_keep_full_totals_records_and_latest_route(app):
import re
from flask import template_rendered
with app.app_context():
add_history(60)
contexts = []
def capture(_app, template, context, **_extra):
contexts.append(context)
template_rendered.connect(capture, app)
client = app.test_client()
login(client)
try:
seen = []
for page, size in ((1, 25), (2, 25), (3, 11)):
response = client.get(f"/dashboard?page={page}")
assert response.status_code == 200
context = contexts[-1]
assert len(context["rows"]) == size
assert context["stats"]["count"] == 61
assert context["stats"]["distance"] == 65
assert context["stats"]["duration"] == 19800
assert context["stats"]["elevation"] == 100
assert context["mapped_run"].id == 1
assert context["record_book"]["records"][0]["seconds"] == 250
assert set(context["record_book"]["runs"]).issubset({row.id for row in context["rows"]})
seen.extend(
int(i)
for i in re.findall(
rb'class="activity-name" href="/activities/(\d+)"', response.data
)
)
assert len(seen) == len(set(seen)) == 61
assert b"Showing 51" in response.data
assert b"1 km PR" in response.data
assert client.get("/dashboard?page=4").status_code == 404
for value in ("0", "-1", "nope", "999999999999999999999", "1.5"):
assert client.get(f"/dashboard?page={value}").status_code == 404
finally:
template_rendered.disconnect(capture, app)
def test_pagination_uses_id_to_break_equal_timestamp_ties(app):
with app.app_context():
add_history(50)
db.session.execute(db.update(Activity).values(started_at=datetime(2026, 1, 1, tzinfo=UTC)))
db.session.commit()
import re
client = app.test_client()
login(client)
ids = []
for page in (1, 2, 3):
ids.extend(
int(i)
for i in re.findall(
rb'class="activity-name" href="/activities/(\d+)"',
client.get(f"/dashboard?page={page}").data,
)
)
assert ids == list(reversed(range(1, 52)))
def test_profile_and_friends_pages_include_only_visible_history(app):
from flask import template_rendered
with app.app_context():
add_history(53, visibility="friends")
db.session.add(Friendship(low_id=1, high_id=2, requested_by=1, status="accepted"))
db.session.commit()
contexts = []
def capture(_app, template, context, **_extra):
contexts.append(context)
template_rendered.connect(capture, app)
client = app.test_client()
login(client, "other")
try:
for path in ("/u/runner", "/friends"):
for page, expected in ((1, 25), (2, 25), (3, 3)):
response = client.get(f"{path}?page={page}")
assert response.status_code == 200
assert len(contexts[-1]["rows"]) == expected
assert contexts[-1]["pagination"].total == 53
assert b"A quiet morning run" not in response.data
if path == "/u/runner":
assert contexts[-1]["stats"]["distance"] == 53
client.post("/friends/1/remove")
assert client.get("/u/runner?page=2").status_code == 404
assert client.get("/friends?page=2").status_code == 404
finally:
template_rendered.disconnect(capture, app)
def test_weekly_sql_totals_respect_sydney_dst_boundaries(app):
from app.history import overview
with app.app_context():
for index, (started, distance) in enumerate(
(
(datetime(2026, 10, 4, 12, 59, 59, tzinfo=UTC), 1000),
(datetime(2026, 10, 4, 13, 0, 0, tzinfo=UTC), 2000),
)
):
db.session.add(
Activity(
owner_id=2,
source_key=f"dst:{index}",
title="DST run",
started_at=started,
distance_m=distance,
duration_s=600,
)
)
db.session.commit()
stats = overview([Activity.owner_id == 2], "Australia/Sydney")
assert stats["weeks"][-2:] == ["28 Sep", "05 Oct"]
assert stats["weekly_km"][-2:] == [1, 2]
assert stats["distance"] == 3
assert stats["count"] == 2
empty = overview([Activity.owner_id == 999], "Australia/Sydney")
assert empty["distance"] == 0
assert empty["pace"] == "—"
assert empty["weekly_km"] == [0] * 8
def test_private_activity_authorization_for_pages_json_and_mutations(app):