M5: end-to-end test matrix and CI

- tests/integration/roundtrip.sh: key, kit code and confirmation,
  location, full and unchanged incremental backup, restore new-folder and
  replace (files identical, extra file in the trash), database dump and
  restore, retention, trash and restore from trash, no files_external
  errors in the log
- tests/integration/matrix.sh: fresh Nextcloud per version and database
  (SQLite, MariaDB, PostgreSQL), removes only its own containers and
  their volumes; locally green on NC32 and NC35 with all three databases
- GitHub Actions: unit tests on PHP 8.2-8.4, round trip on NC32-35 x
  SQLite/MariaDB/PostgreSQL

Fixes found by the matrix:
- files_external is no longer booted when it is disabled: only its
  classes are loaded and a private BackendService gets its backends from
  its Application class (booting registered its mount provider, which
  failed on installations where its tables were never created)
- SQLite: the database file is excluded from the file backup (it is
  covered by the logical dump and copying the live file is inconsistent)
- occ backup:key:confirm accepts --code and --confirm for scripts
This commit is contained in:
2026-10-02 11:56:57 +02:00
parent 48bb829d45
commit c54b2cebed
7 changed files with 189 additions and 11 deletions
+26 -4
View File
@@ -121,11 +121,33 @@ class TargetService {
return new StorageBackend($this->factory()->create($backend, $auth, $options), $basePath);
}
private ?ExternalStorageFactory $factory = null;
/**
* files_external's connection code, without changing the app's state:
* - enabled: it is booted by Nextcloud; use its own BackendService;
* - disabled: do NOT boot it (booting registers its mount provider and listeners, which use
* its database tables; on installations where it was never enabled those do not exist).
* Only its classes are loaded, and a private BackendService gets its backends and auth
* mechanisms from its Application class.
*/
private function factory(): ExternalStorageFactory {
// Load files_external's classes without enabling the app (its state stays as the admin set it).
if (!class_exists(\OCA\Files_External\Service\BackendService::class)) {
$this->appManager->loadApp('files_external');
if ($this->factory !== null) {
return $this->factory;
}
return new ExternalStorageFactory(Server::get(\OCA\Files_External\Service\BackendService::class));
if ($this->appManager->isEnabledForAnyone('files_external')) {
$this->appManager->loadApp('files_external');
return $this->factory = new ExternalStorageFactory(Server::get(\OCA\Files_External\Service\BackendService::class));
}
if (!class_exists(\OCA\Files_External\AppInfo\Application::class)) {
$path = $this->appManager->getAppPath('files_external');
require_once $path . '/composer/autoload.php';
}
$service = new \OCA\Files_External\Service\BackendService(
Server::get(\OCP\IAppConfig::class), Server::get(\Psr\Log\LoggerInterface::class));
$app = new \OCA\Files_External\AppInfo\Application();
$service->registerBackendProvider($app);
$service->registerAuthMechanismProvider($app);
return $this->factory = new ExternalStorageFactory($service);
}
}