From b180cc8b261433e0f9cd4d1ef88cd6c401858bd4 Mon Sep 17 00:00:00 2001 From: Iva Horn Date: Fri, 6 Mar 2026 15:00:55 +0100 Subject: [PATCH 1/2] fix(loopback): set test_dummy_token before first HTTP request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `circles:check` loopback test always fails with a 401 on the POST request to `asyncBroadcast` with `test-dummy-token` when the config key `test_dummy_token` does not already exist in the database. Root cause: The GET request to `core.CSRFToken.index` (which runs first) causes the web server process to load all `appconfig` values into its APCu local cache (TTL=3s). Only then does the CLI process insert `test_dummy_token` into the database and clear its own APCu cache — but CLI and web server run in separate SAPI contexts with independent APCu stores, so the web cache is unaffected. When the POST request arrives shortly after the GET (well within the 3-second TTL), the web process reads from its still-valid APCu cache, which does not contain the newly inserted key. `getValueInt()` therefore returns the default `0`, and `0 < time()` evaluates to true, causing the controller to return 401. Moving `setValueInt()` before the GET request ensures the key exists in the database before any web request loads the appconfig into APCu. Signed-Off-By: Iva Horn Co-Authored-By: Claude Opus 4.6 --- lib/Command/CirclesCheck.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Command/CirclesCheck.php b/lib/Command/CirclesCheck.php index 27581c568..f41d6bc60 100644 --- a/lib/Command/CirclesCheck.php +++ b/lib/Command/CirclesCheck.php @@ -254,11 +254,12 @@ private function setupLoopback(InputInterface $input, OutputInterface $output, s * @throws UnknownRemoteException */ private function testLoopback(InputInterface $input, OutputInterface $output): bool { + $this->appConfig->setValueInt(Application::APP_ID, 'test_dummy_token', time() + 10); + if (!$this->testRequest($output, 'GET', 'core.CSRFToken.index')) { return false; } - $this->appConfig->setValueInt(Application::APP_ID, 'test_dummy_token', time() + 10); if (!$this->testRequest( $output, 'POST', 'circles.EventWrapper.asyncBroadcast', ['token' => 'test-dummy-token'] From 4ed7eef6ca122c957b4af9f54eab801902a5bec3 Mon Sep 17 00:00:00 2001 From: Iva Horn Date: Fri, 6 Mar 2026 15:01:13 +0100 Subject: [PATCH 2/2] fix(loopback): allow wrapper creation for loopback tests from CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even after the 401 is resolved, `circles:check` still fails with "Event created too many Wrappers" (actually zero wrappers found, the message is misleading). The LoopbackTest FederatedEvent never gets an EventWrapper created when running from CLI. Root cause: `initBroadcast()` has an early-return guard: if (empty($instances) && (!$event->isAsync() || OC::$CLI)) For a LoopbackTest event (which implements IFederatedItemAsyncProcess but has no remote instances), `$instances` is empty. Combined with `OC::$CLI === true`, the guard returns false — skipping all wrapper creation. This optimisation is correct for regular async events in CLI mode, where `newEvent()` already calls `manage()` synchronously and no async broadcast is needed. However, the LoopbackTest specifically exists to verify that the async loopback path works end-to-end: wrapper creation, async POST, `manageWrapper()`, and `confirmStatus()` must all run to produce the verify/manage values that `testLoopback()` asserts. The fix exempts events whose class implements IFederatedItemLoopbackTest from the CLI guard, so that a wrapper is created and the async broadcast is triggered even when running from CLI. The double call to `manage()` (once synchronously in `newEvent()`, once asynchronously via the wrapper) is harmless for LoopbackTest since it only sets an idempotent result value. Signed-Off-By: Iva Horn Co-Authored-By: Claude Opus 4.6 --- lib/Service/FederatedEventService.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Service/FederatedEventService.php b/lib/Service/FederatedEventService.php index 25e0e6088..a63998445 100644 --- a/lib/Service/FederatedEventService.php +++ b/lib/Service/FederatedEventService.php @@ -386,7 +386,9 @@ private function configureEvent(FederatedEvent $event, IFederatedItem $item) { public function initBroadcast(FederatedEvent $event): bool { $instances = $this->getInstances($event); // if empty instance and ran from CLI, any action as already been managed - if (empty($instances) && (!$event->isAsync() || OC::$CLI)) { + // except for loopback tests which need async verification even from CLI + $isLoopbackTest = is_a($event->getClass(), IFederatedItemLoopbackTest::class, true); + if (empty($instances) && (!$event->isAsync() || (OC::$CLI && !$isLoopbackTest))) { return false; }