fix: grow Wiki.js listing limit until page count stabilizes

Live evidence during quality-report verification: pages.list(limit=100)
returned 43 pages while 140 existed; limit=500 returned all 140. Wiki.js
applies the limit BEFORE its own visibility filtering, so a response with
fewer pages than requested does NOT prove the listing is complete. The
Phase A limit-growth loop stopped on len < limit and silently truncated
listings (page counts, orphan cleanups, integrity scans, and the quality
report all consume this listing).

The loop now doubles the limit until the returned count stops increasing
(fixed point), at the cost of one confirming fetch. Offline pagination
tests updated, including a regression test simulating the pre-filter
limit behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbFZyDvYksazX6nYQYZ67L
This commit is contained in:
2026-07-14 12:19:35 +02:00
co-authored by Claude Fable 5
parent 8f56b78be7
commit 77dc5b00a1
3 changed files with 45 additions and 12 deletions
+32 -8
View File
@@ -1,9 +1,11 @@
"""
Unit tests for WikiJSClient page listing and pagination (offline).
Wiki.js 2.x `pages.list` supports only a `limit` argument (no offset), so
exhaustive listing works by growing the limit until the API returns fewer
pages than requested. These tests mock the GraphQL layer.
Wiki.js 2.x `pages.list` supports only a `limit` argument (no offset), and
the limit is applied BEFORE Wiki.js's own visibility filter — fewer pages
than requested does NOT mean the listing is complete. Exhaustive listing
therefore grows the limit until the returned count stops increasing.
These tests mock the GraphQL layer.
"""
import pytest
@@ -44,17 +46,39 @@ def client():
class TestListAllPagesPagination:
"""list_all_pages must exhaust the listing via a limit-growth loop."""
async def test_grows_limit_until_exhausted(self, client):
async def test_grows_limit_until_count_stabilizes(self, client):
pages = await client.list_all_pages(batch_size=50)
# 50 -> full batch, 100 -> full batch, 200 -> 138 < 200 done
assert client._requested_limits == [50, 100, 200]
# 50 -> 50, 100 -> 100, 200 -> 138, 400 -> 138 (stable) done
assert client._requested_limits == [50, 100, 200, 400]
assert len(pages) == 138
async def test_single_call_when_first_batch_not_full(self, client):
async def test_confirms_completeness_with_second_fetch(self, client):
"""A single not-full batch is NOT trusted (limit precedes Wiki.js's
visibility filter); a confirming fetch at a doubled limit runs."""
pages = await client.list_all_pages(batch_size=500)
assert client._requested_limits == [500]
assert client._requested_limits == [500, 1000]
assert len(pages) == 138
async def test_pre_filter_limit_does_not_truncate(self, client):
"""Regression (observed live): Wiki.js applies `limit` before its
visibility filter, so limit=100 returned 43 pages while 140 existed.
The old `len < limit` stop condition silently dropped pages."""
all_pages = client._all_pages
async def fake_execute(query, variables=None):
limit = variables["limit"]
client._requested_limits.append(limit)
# Only ~half the pages within the limit window are visible
return {"pages": {"list": all_pages[: limit // 2]}}
client._execute_query = fake_execute
pages = await client.list_all_pages(batch_size=100)
# 100 -> 50 (< limit, but NOT complete), 200 -> 100, 400 -> 138,
# 800 -> 138 (stable) done
assert len(pages) == 138
async def test_path_prefix_filter_after_exhaustion(self, client):