Fix following past 48 entries and make the e621 page size configurable
Follow lists were paginated at the API default of 48, but the SPA treats them as complete sets: the tag/pool toggles read their state from page one (so the 49th follow looked unfollowed and its spinner waited for a page that could never contain it) and the Followed page rendered only 48 cards while showing that as the count. Both follow endpoints are now unpaginated — they are per-user sets and still restricted to the caller's rows — and the three consumers take plain arrays. Post visibility: the old J621-Django online view fetched limit=320 (e621's maximum) while ours hard-coded 48, and fetchPostsByIds capped id batches at 100. The Online browser now has a 'Posts per page' setting (48/100/200/320) in its sidebar, mirrored in Account -> Browsing preferences, stored per user as e621_per_page and also used for pool loading; the id-batch cap is raised to 320. Tests: follow list shape/isolation (4) and preference validation/merge (3) added; the full backend suite is 46 green. Live-checked the array response shape and the preference bounds (200 accepted, 500 rejected).
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""The follow lists are complete sets, not pages.
|
||||
|
||||
The SPA uses `GET /api/follows/tags/` (and pools) for two things: deciding a
|
||||
follow toggle's state, and rendering every card on the Followed page. With the
|
||||
standard 48-item pagination that silently broke past 48 follows — a 49th tag
|
||||
could not be followed (the toggle never saw it) and the page hid it. These
|
||||
tests pin the unpaginated behaviour.
|
||||
"""
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from apps.follows.models import FollowedPool, FollowedTag
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class FollowListTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="follow-user", password="follow-pass-123456"
|
||||
)
|
||||
self.other = User.objects.create_user(
|
||||
username="follow-other", password="follow-pass-123456"
|
||||
)
|
||||
self.client = self.api_client(self.user)
|
||||
self.other_client = self.api_client(self.other)
|
||||
|
||||
def api_client(self, user):
|
||||
client = Client()
|
||||
client.defaults["HTTP_AUTHORIZATION"] = (
|
||||
f"Token {Token.objects.create(user=user).key}"
|
||||
)
|
||||
return client
|
||||
|
||||
def test_tag_list_is_not_paginated_past_48(self):
|
||||
FollowedTag.objects.bulk_create(
|
||||
[
|
||||
FollowedTag(user=self.user, tag=f"tag_{index:03d}")
|
||||
for index in range(60)
|
||||
]
|
||||
)
|
||||
response = self.client.get("/api/follows/tags/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json()
|
||||
self.assertIsInstance(data, list, "the list must not be paginated")
|
||||
self.assertEqual(len(data), 60)
|
||||
self.assertEqual(data[0]["tag"], "tag_000")
|
||||
self.assertEqual(data[-1]["tag"], "tag_059")
|
||||
# The shape the SPA uses for its follow state.
|
||||
self.assertIn("unseen_count", data[0])
|
||||
self.assertIn("id", data[0])
|
||||
|
||||
def test_pool_list_is_not_paginated(self):
|
||||
FollowedPool.objects.bulk_create(
|
||||
[
|
||||
FollowedPool(user=self.user, pool_id=59000 + index, name=f"pool {index}")
|
||||
for index in range(55)
|
||||
]
|
||||
)
|
||||
data = self.client.get("/api/follows/pools/").json()
|
||||
self.assertIsInstance(data, list)
|
||||
self.assertEqual(len(data), 55)
|
||||
|
||||
def test_lists_only_contain_the_callers_follows(self):
|
||||
FollowedTag.objects.create(user=self.user, tag="mine")
|
||||
FollowedTag.objects.create(user=self.other, tag="theirs")
|
||||
|
||||
mine = self.client.get("/api/follows/tags/").json()
|
||||
theirs = self.other_client.get("/api/follows/tags/").json()
|
||||
self.assertEqual([row["tag"] for row in mine], ["mine"])
|
||||
self.assertEqual([row["tag"] for row in theirs], ["theirs"])
|
||||
|
||||
def test_anonymous_cannot_list(self):
|
||||
self.assertEqual(Client().get("/api/follows/tags/").status_code, 401)
|
||||
self.assertEqual(Client().get("/api/follows/pools/").status_code, 401)
|
||||
Reference in New Issue
Block a user