From eca975a248cde6c84e0fb1aba30a9556b1e2c683 Mon Sep 17 00:00:00 2001 From: Conduction Release Bot Date: Sat, 22 Aug 2026 01:30:16 +0200 Subject: [PATCH] =?UTF-8?q?fix(tests):=20opt=20into=20the=20OpenRegister?= =?UTF-8?q?=20contract=20=E2=80=94=20fixes=20130=20standalone=20unit=20err?= =?UTF-8?q?ors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepares this app for ConductionNL/.github#531, which drops `OCA\OpenRegister\Contract\` from conduction/hydra-gates' RUNTIME psr-4 autoload. That prefix is LONGER than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is longest-prefix-wins, so whichever app's autoloader registers first defines OpenRegister's contract for the whole process. IT ALSO FIXES A LIVE PROBLEM HERE, WHICH THE OTHER APPS IN THIS SWEEP DID NOT HAVE. tests/bootstrap-unit.php registers its OpenRegister stubs through a manual spl_autoload_register prefix map covering `OCA\OpenRegister\Db\` and `...\Service\`. Neither covers `...\Contract\`, so the standalone unit suite was already failing on it: before Tests: 715, Assertions: 2573, Errors: 131, Failures: 1, Skipped: 25 after Tests: 715, Assertions: 2993, Errors: 1, Failures: 0, Skipped: 24 The single remaining error is unrelated — Symfony\Component\HttpFoundation\ HeaderUtils is absent from the standalone environment, reached via OCP's DownloadResponse. Added to BOTH bootstraps deliberately: phpunit.xml loads tests/bootstrap.php and phpunit-unit.xml loads tests/bootstrap-unit.php, and both reach code that needs the contract. The full phpunit.xml path cannot be measured outside a Nextcloud tree (its bootstrap fatals on `Class "OC_App" not found`), so that half is verified by CI rather than locally, and is a no-op there while the prefix still exists. interface_exists() is order-independent: it asks whether the interface is RESOLVABLE rather than who registered first. Appending a fallback autoloader does not work, because spl_autoload_register appends relative to registration order and that order across independently loaded apps is what nobody controls. --- tests/bootstrap-unit.php | 25 +++++++++++++++++++++++++ tests/bootstrap.php | 26 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index c1939566..7001ac87 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -23,6 +23,31 @@ // Include Composer's autoloader. require_once __DIR__ . '/../vendor/autoload.php'; +// THE OpenRegister CONTRACT INTERFACES, OPTED INTO RATHER THAN AUTOLOADED. +// +// conduction/hydra-gates claims `OCA\OpenRegister\Contract\` as a RUNTIME psr-4 +// prefix, so consumers get these interfaces implicitly. That prefix is LONGER +// than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is +// longest-prefix-wins, so whichever app's autoloader registers first defines +// OpenRegister's contract for the whole process (ConductionNL/.github#531). +// +// Note the stub prefixes registered just below are `OCA\OpenRegister\Db\` and +// `...\Service\` — neither covers `...\Contract\`, so once hydra-gates stops +// declaring it nothing else in this app resolves it. +// +// interface_exists() is order-independent — it asks whether the interface is +// RESOLVABLE, not who registered first. Appending a fallback autoloader does +// NOT work: spl_autoload_register appends relative to registration order, and +// that order across independently loaded apps is what nobody controls. +foreach (['ObjectEntityInterface', 'ObjectServiceInterface'] as $contract) { + if (interface_exists('\\OCA\\OpenRegister\\Contract\\' . $contract) === false) { + $shipped = __DIR__ . '/../vendor/conduction/hydra-gates/hydra-gates/contracts/' . $contract . '.php'; + if (file_exists($shipped) === true) { + require_once $shipped; + } + } +} + // Register OCP/NCU classes from nextcloud/ocp package. // nextcloud/ocp has no autoload section in its composer.json, so we register it manually. spl_autoload_register(function (string $class): void { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 4174546e..7783ea29 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -23,6 +23,32 @@ // Include Composer's autoloader require_once __DIR__ . '/../vendor/autoload.php'; +// THE OpenRegister CONTRACT INTERFACES, OPTED INTO RATHER THAN AUTOLOADED. +// +// conduction/hydra-gates claims `OCA\OpenRegister\Contract\` as a RUNTIME psr-4 +// prefix, so consumers get these interfaces implicitly. That prefix is LONGER +// than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is +// longest-prefix-wins, so whichever app's autoloader registers first defines +// OpenRegister's contract for the whole process (ConductionNL/.github#531). +// +// Loaded here, immediately after the autoloader and before the OpenRegister +// stubs below, for the same reason those stubs are loaded early: what is +// declared first wins, and the contract must exist before anything implementing +// it is declared. +// +// interface_exists() is order-independent — it asks whether the interface is +// RESOLVABLE, not who registered first. Appending a fallback autoloader does +// NOT work: spl_autoload_register appends relative to registration order, and +// that order across independently loaded apps is what nobody controls. +foreach (['ObjectEntityInterface', 'ObjectServiceInterface'] as $contract) { + if (interface_exists('\\OCA\\OpenRegister\\Contract\\' . $contract) === false) { + $shipped = __DIR__ . '/../vendor/conduction/hydra-gates/hydra-gates/contracts/' . $contract . '.php'; + if (file_exists($shipped) === true) { + require_once $shipped; + } + } +} + // OpenRegister test stubs. The real OCA\OpenRegister\Db\ObjectEntity has // __call magic getters that PHPUnit cannot configure on a mock, so the unit // tests use the explicit stub in tests/Stubs/. It is loaded HERE, BEFORE