fix(shop): a malformed 200 from GitHub is a failure, not an empty result - #428
Conversation
The App store has been rendering neither installable cards nor its "GitHub could not be reached" hint, and e2e REQ-OBTC-006 has failed on development since 14:46 today. GitHubCatalogService::search() decoded the response and, whenever the body would not parse or carried no `items` array, quietly left `$items = []` and fell through to OUTCOME_OK. So a proxy error page or a truncated response was reported as a successful search that simply matched nothing. That produced the dead state the test caught. OUTCOME_OK means `githubUnavailable` is false, so TemplateGallery renders no hint; zero cards means it renders "No GitHub apps match your search" instead. The user is told their query found nothing, when in fact the lookup never completed. A lookup failure must not wear the words of a judgement. An unparseable or unshaped 200 now returns OUTCOME_UNREACHABLE, which the UI already knows how to show. The early return also skips the cache write, so one malformed response is no longer served to every later caller for the rest of the TTL. Two tests, because fixing this by calling every empty result unreachable would replace one lie with another: - a 200 whose body is not the search shape yields OUTCOME_UNREACHABLE - a well-formed but genuinely empty result set stays OUTCOME_OK Verified the first test fails without the production change and passes with it. Spec gains the matching scenario under REQ-GHSC-003. phpcs clean, psalm 0 errors, phpstan OK, 883 unit tests pass.
Quality Report — ConductionNL/buildiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 625/625 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-23 22:43 UTC
Download the full PDF report from the workflow artifacts.
Correction: this does not fix REQ-OBTC-006I opened this PR saying it fixes the failing E2E. It does not. What this PR does fix, and still shouldThe malformed-body path is a real defect on its own terms: a 200 whose body will not parse was reported as What is actually failing in CI
So the UI renders the "no apps match your search" empty state, and the test asserts it must render either installable cards or the unavailable hint, and never neither. That premise does not hold for a real empty result set. One of these has to give:
That is a product/spec decision on Merging this as a correct partial fix, since it does not cause or worsen the failure and |
…ister One conflict, src/manifest.json's walkthrough steps, and both sides had changed it for different reasons: development rewrote the copy in the voice sweep (#426) while this branch renamed the register the `create-app` step watches. Took development's wording with this branch's `"register": "buildiq"`, so neither change is lost. All 13 register references in the manifest now read buildiq and the file still parses. Development also brought #428, "a malformed 200 from GitHub is a failure, not an empty result". That is complementary to the e2e change on this branch rather than a duplicate of it: #428 reclassifies a MALFORMED response so it renders the unavailable hint, while the third state this branch teaches the test about is a SUCCESSFUL search that matched nothing — which the view still renders as `.template-gallery__empty`. Both states exist; the assertion now covers all three.
The symptom
The App store renders neither installable cards nor its "GitHub could not be reached" hint. E2E
REQ-OBTC-006has failed ondevelopmentsince 14:46 today (last green 07:03), so this is not a passing blip.The cause
GitHubCatalogService::search()decoded the response and, whenever the body would not parse or carried noitemsarray, left$items = []and fell through toOUTCOME_OK:A proxy error page or a truncated response was therefore reported as a successful search that simply matched nothing.
That is exactly the dead state the test catches:
OUTCOME_OKgithubUnavailableis false, andTemplateGalleryrenders no hintThe user is told their query found nothing, when in fact the lookup never completed. A lookup failure must not wear the words of a judgement.
The
ok === falsepath and the controller'sThrowablehandler were both already correct. This was the one branch where a non-answer became an answer.The fix
An unparseable or unshaped 200 now returns
OUTCOME_UNREACHABLE, which the UI already knows how to render. The early return also skips the cache write, so one malformed response is no longer served to every later caller for the rest of the TTL.Tests
Two, deliberately, because "fix" this by calling every empty result unreachable and you have replaced one lie with another:
OUTCOME_UNREACHABLEOUTCOME_OKI verified the first test fails without the production change and passes with it. A test that would pass either way pins nothing.
Checks
phpcs(project scope)psalmphpstan[OK] No errorsphpunitunit suiteSpec gains the matching scenario under
REQ-GHSC-003, carrying an@e2e excludewith its reason: the malformed-body branch cannot be driven from a browser without making GitHub itself return a bad 200, and the user-visible consequence is already covered byREQ-OBTC-006.Found while running the new
writingskill's REVIEW mode over buildiq (ConductionNL/hydra#610) and following its E2E failure to the cause.