Files
JakeBreath 1adb761c8d 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).
2026-09-18 19:06:18 -05:00

79 lines
3.0 KiB
Python

"""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)