Paginate activity history and aggregate dashboard totals in SQL
This commit is contained in:
+165
-1
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user