Skip to content

refactor(appstore): walk both catalogue endpoints with one page loop - #557

Merged
rubenvdlinde merged 2 commits into
developmentfrom
refactor/sig-appstore-fetch
Nov 2, 2026
Merged

rubenvdlinde merged 2 commits into
developmentfrom
refactor/sig-appstore-fetch

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What

AppStoreSource::fetchAppPayloadUncached was one 70-line unit with two copies of the same page loop: one over the filtered apps.json?filter= endpoint, one over the platform platform/<version>/apps.json endpoint. They now share one loop:

  • CatalogueQuery (new, lib/Service/Source/) holds the app id, a closure that builds each page's endpoint, and whether an empty or non-array body ends the whole lookup (the filtered endpoint, as before) or only skips that page (the platform endpoint, as before);
  • searchCatalogue walks the pages up to MAX_PAGES: it marks the store as answered, caches every entry in the page, returns the app when found, and stops when no next page can exist;
  • readCataloguePage fetches and decodes one page and notes each failure for the connection report with the same reasons as before.

The filtered endpoint still reads the API base on every page, and the platform version is still read before the platform API base, so every request and config read happens in the same order. The difference between the two loops (a bad body on the filtered endpoint ends the lookup) is kept, not unified.

SIG numbers (gate 122, five-star profile, php)

check development this branch 4-star 5-star
size >15 64.13% 64.06% 47.1% 23.6%
size >30 36.15% 35.33% 23.1% 11.6%
size >60 13.95% 13.11% 8.3% 4.2%
CCN >5 53.65% 53.08% 20.2% 10.1%
CCN >10 29.78% 28.95% 7.3% 3.7%
params >=3 31.42% 31.43% 15.0% 7.5%
duplication 5.43% 5.18% met 2.8%

The 70-line unit leaves the >30 and >60 bands; the largest new step is 23 lines, complexity 8, two parameters. The 0.01 rise in params >=3 is the denominator only: the unit LOC in that band stays at 2,629 while total unit LOC drops by 2 (8,367 to 8,365). CatalogueQuery takes two constructor arguments; the stop-on-bad-body flag is a property, so no new unit has three parameters.

Verified

  • Golden test tests/unit/Service/Source/AppStoreFetchGoldenTest.php runs 58 scenarios: 16 response shapes (transport error, 500, 404, a blank body, non-array JSON, invalid JSON, the app in data, in a plain list or in apps, and pages without the app that signal next through pages.next, pagination.next_page, nextPage or a wrong page, an empty list, data not a list) on the filtered endpoint, on the platform endpoint and on the platform endpoint alone, plus endless next pages on one and both endpoints, endless errors, the app on page three, odd and short server versions and a mirror API base. Per case it compares, as JSON text, every request with its options, every config and app-config read and write (timestamps masked), the returned payload and the connection outcome. Recorded on the old code in the first commit, re-run plain on old and new: identical.
  • Mutants (prlimit --as=1.5G timeout 120, signal-safe runner, file restored after each round): new code 15 of 15 killed, old code 5 of 5 killed, the no-op control survives in both.
  • Unit suite (tests/phpunit-unit-only.xml): 758 tests OK. Under pcov: 758 OK, 0 risky. Psalm (level 1, full lib): no errors. php-cs-fixer: 0 to fix. Gate 122 ratchet: 0 findings (5 inherited). Gate 16: count 0. Gate 119: 0.

One difference that cannot be observed: before, an exception from caching, payload extraction or the next-page check inside a page was also caught as "no readable answer". Those helpers do not throw (the cache write already swallows its own errors), so only the request and the JSON decode sit inside the catch now.

AI-assisted (Claude Code, claude-opus-5-5).

…io set

Assisted-by: ClaudeCode:claude-opus-5-5
The filtered and the platform endpoint ran the same page loop twice;
they now share one, with a CatalogueQuery naming each endpoint and
whether a bad body ends the lookup. Behaviour, every request and the
connection outcome are unchanged (pinned by the golden scenario set).

Assisted-by: ClaudeCode:claude-opus-5-5
@rubenvdlinde
rubenvdlinde merged commit 3e7c93c into development Nov 2, 2026
7 checks passed
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/versioniq @ 823d94e

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
composer ✅ ✅ 32/32
npm ❌ ✅ 302/302
app:check-code ⏭️
info.xml ✅
REUSE ❌
lockfile sync ✅
PHPUnit ✅ 759/759 (8 legs)
Newman ⏭️
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ✅

Quality workflow — 2026-11-02 10:01 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant