aboutsummaryrefslogtreecommitdiffstats
path: root/apps/testing
diff options
context:
space:
mode:
Diffstat (limited to 'apps/testing')
-rw-r--r--apps/testing/.noopenapi0
-rw-r--r--apps/testing/appinfo/info.xml22
-rw-r--r--apps/testing/appinfo/routes.php105
-rw-r--r--apps/testing/clean_opcode_cache.php7
-rw-r--r--apps/testing/composer/autoload.php25
-rw-r--r--apps/testing/composer/composer.json13
-rw-r--r--apps/testing/composer/composer.lock18
-rw-r--r--apps/testing/composer/composer/ClassLoader.php579
-rw-r--r--apps/testing/composer/composer/InstalledVersions.php359
-rw-r--r--apps/testing/composer/composer/LICENSE21
-rw-r--r--apps/testing/composer/composer/autoload_classmap.php33
-rw-r--r--apps/testing/composer/composer/autoload_namespaces.php9
-rw-r--r--apps/testing/composer/composer/autoload_psr4.php10
-rw-r--r--apps/testing/composer/composer/autoload_real.php37
-rw-r--r--apps/testing/composer/composer/autoload_static.php59
-rw-r--r--apps/testing/composer/composer/installed.json5
-rw-r--r--apps/testing/composer/composer/installed.php23
-rw-r--r--apps/testing/config.php70
-rw-r--r--apps/testing/img/app.svg5
-rw-r--r--apps/testing/img/logo.pngbin0 -> 3571 bytes
-rw-r--r--apps/testing/lib/AlternativeHomeUserBackend.php45
-rw-r--r--apps/testing/lib/AppInfo/Application.php78
-rw-r--r--apps/testing/lib/Controller/ConfigController.php50
-rw-r--r--apps/testing/lib/Controller/LockingController.php187
-rw-r--r--apps/testing/lib/Controller/RateLimitTestController.php37
-rw-r--r--apps/testing/lib/Conversion/ConversionProvider.php47
-rw-r--r--apps/testing/lib/HiddenGroupBackend.php44
-rw-r--r--apps/testing/lib/Listener/GetDeclarativeSettingsValueListener.php37
-rw-r--r--apps/testing/lib/Listener/RegisterDeclarativeSettingsListener.php71
-rw-r--r--apps/testing/lib/Listener/SetDeclarativeSettingsValueListener.php37
-rw-r--r--apps/testing/lib/Locking/FakeDBLockingProvider.php48
-rw-r--r--apps/testing/lib/Migration/Version30000Date20240102030405.php41
-rw-r--r--apps/testing/lib/Provider/FakeText2ImageProvider.php33
-rw-r--r--apps/testing/lib/Provider/FakeTextProcessingProvider.php39
-rw-r--r--apps/testing/lib/Provider/FakeTextProcessingProviderSync.php45
-rw-r--r--apps/testing/lib/Provider/FakeTranslationProvider.php29
-rw-r--r--apps/testing/lib/Settings/DeclarativeSettingsForm.php205
-rw-r--r--apps/testing/lib/TaskProcessing/FakeContextWriteProvider.php130
-rw-r--r--apps/testing/lib/TaskProcessing/FakeTextToImageProvider.php108
-rw-r--r--apps/testing/lib/TaskProcessing/FakeTextToTextProvider.php122
-rw-r--r--apps/testing/lib/TaskProcessing/FakeTextToTextSummaryProvider.php122
-rw-r--r--apps/testing/lib/TaskProcessing/FakeTranscribeProvider.php88
-rw-r--r--apps/testing/lib/TaskProcessing/FakeTranslateProvider.php154
43 files changed, 3076 insertions, 121 deletions
diff --git a/apps/testing/.noopenapi b/apps/testing/.noopenapi
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/apps/testing/.noopenapi
diff --git a/apps/testing/appinfo/info.xml b/apps/testing/appinfo/info.xml
index 4c07ed87d01..66614f35c8c 100644
--- a/apps/testing/appinfo/info.xml
+++ b/apps/testing/appinfo/info.xml
@@ -1,12 +1,24 @@
-<?xml version="1.0"?>
-<info>
+<?xml version="1.0"?>
+<!--
+ - SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-FileCopyrightText: 2015 owncloud, Inc.
+ - SPDX-License-Identifier: AGPL-3.0-only
+-->
+<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>testing</id>
<name>QA testing</name>
+ <summary>This app is only for testing! It is dangerous to have it enabled in a live instance</summary>
<description>This app is only for testing! It is dangerous to have it enabled in a live instance</description>
- <licence>AGPL</licence>
+ <version>1.22.0</version>
+ <licence>agpl</licence>
<author>Joas Schilling</author>
- <version>0.1.0</version>
+ <types>
+ <authentication/>
+ </types>
+ <category>monitoring</category>
+ <bugs>https://github.com/nextcloud/server/issues</bugs>
<dependencies>
- <owncloud min-version="9.1" max-version="9.1" />
+ <nextcloud min-version="32" max-version="32"/>
</dependencies>
</info>
diff --git a/apps/testing/appinfo/routes.php b/apps/testing/appinfo/routes.php
index 6684d486360..862f63ef4c2 100644
--- a/apps/testing/appinfo/routes.php
+++ b/apps/testing/appinfo/routes.php
@@ -1,46 +1,67 @@
<?php
+
/**
- * @author Joas Schilling <nickvergessen@owncloud.com>
- *
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2015-2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
+return [
+ 'routes' => [
+ [
+ 'name' => 'RateLimitTest#userAndAnonProtected',
+ 'url' => '/userAndAnonProtected',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'RateLimitTest#onlyAnonProtected',
+ 'url' => '/anonProtected',
+ 'verb' => 'GET',
+ ],
+ ],
-namespace OCA\Testing\AppInfo;
-
-use OCA\Testing\Config;
-use OCP\API;
-
-$config = new Config(
- \OC::$server->getConfig(),
- \OC::$server->getRequest()
-);
-
-API::register(
- 'post',
- '/apps/testing/api/v1/app/{appid}/{configkey}',
- [$config, 'setAppValue'],
- 'testing',
- API::ADMIN_AUTH
-);
-
-API::register(
- 'delete',
- '/apps/testing/api/v1/app/{appid}/{configkey}',
- [$config, 'deleteAppValue'],
- 'testing',
- API::ADMIN_AUTH
-);
+ 'ocs' => [
+ [
+ 'name' => 'Config#setAppValue',
+ 'url' => '/api/v1/app/{appid}/{configkey}',
+ 'verb' => 'POST',
+ ],
+ [
+ 'name' => 'Config#deleteAppValue',
+ 'url' => '/api/v1/app/{appid}/{configkey}',
+ 'verb' => 'DELETE',
+ ],
+ [
+ 'name' => 'Locking#isLockingEnabled',
+ 'url' => '/api/v1/lockprovisioning',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'Locking#isLocked',
+ 'url' => '/api/v1/lockprovisioning/{type}/{user}',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'Locking#acquireLock',
+ 'url' => '/api/v1/lockprovisioning/{type}/{user}',
+ 'verb' => 'POST',
+ ],
+ [
+ 'name' => 'Locking#changeLock',
+ 'url' => '/api/v1/lockprovisioning/{type}/{user}',
+ 'verb' => 'PUT',
+ ],
+ [
+ 'name' => 'Locking#releaseLock',
+ 'url' => '/api/v1/lockprovisioning/{type}/{user}',
+ 'verb' => 'DELETE',
+ ],
+ [
+ 'name' => 'Locking#releaseAll',
+ 'url' => '/api/v1/lockprovisioning/{type}',
+ 'verb' => 'DELETE',
+ 'defaults' => [
+ 'type' => null
+ ]
+ ],
+ ],
+];
diff --git a/apps/testing/clean_opcode_cache.php b/apps/testing/clean_opcode_cache.php
new file mode 100644
index 00000000000..acfeece7e15
--- /dev/null
+++ b/apps/testing/clean_opcode_cache.php
@@ -0,0 +1,7 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+opcache_reset();
diff --git a/apps/testing/composer/autoload.php b/apps/testing/composer/autoload.php
new file mode 100644
index 00000000000..4c194aa5707
--- /dev/null
+++ b/apps/testing/composer/autoload.php
@@ -0,0 +1,25 @@
+<?php
+
+// autoload.php @generated by Composer
+
+if (PHP_VERSION_ID < 50600) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, $err);
+ } elseif (!headers_sent()) {
+ echo $err;
+ }
+ }
+ trigger_error(
+ $err,
+ E_USER_ERROR
+ );
+}
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInitTesting::getLoader();
diff --git a/apps/testing/composer/composer.json b/apps/testing/composer/composer.json
new file mode 100644
index 00000000000..769aaf70fd7
--- /dev/null
+++ b/apps/testing/composer/composer.json
@@ -0,0 +1,13 @@
+{
+ "config" : {
+ "vendor-dir": ".",
+ "optimize-autoloader": true,
+ "classmap-authoritative": true,
+ "autoloader-suffix": "Testing"
+ },
+ "autoload" : {
+ "psr-4": {
+ "OCA\\Testing\\": "../lib/"
+ }
+ }
+}
diff --git a/apps/testing/composer/composer.lock b/apps/testing/composer/composer.lock
new file mode 100644
index 00000000000..fd0bcbcb753
--- /dev/null
+++ b/apps/testing/composer/composer.lock
@@ -0,0 +1,18 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "d751713988987e9331980363e24189ce",
+ "packages": [],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.1.0"
+}
diff --git a/apps/testing/composer/composer/ClassLoader.php b/apps/testing/composer/composer/ClassLoader.php
new file mode 100644
index 00000000000..7824d8f7eaf
--- /dev/null
+++ b/apps/testing/composer/composer/ClassLoader.php
@@ -0,0 +1,579 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var string|null */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array<string, array<string, int>>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array<string, list<string>>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var list<string>
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * List of PSR-0 prefixes
+ *
+ * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+ *
+ * @var array<string, array<string, list<string>>>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var list<string>
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var array<string, string>
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var array<string, bool>
+ */
+ private $missingClasses = array();
+
+ /** @var string|null */
+ private $apcuPrefix;
+
+ /**
+ * @var array<string, self>
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param string|null $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return array<string, list<string>>
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array<string, list<string>>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return list<string>
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return list<string>
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return array<string, string> Array of classname => path
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array<string, string> $classMap Class to filename map
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list<string>|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list<string>|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list<string>|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list<string>|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ $includeFile = self::$includeFile;
+ $includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ *
+ * @return array<string, self>
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
+}
diff --git a/apps/testing/composer/composer/InstalledVersions.php b/apps/testing/composer/composer/InstalledVersions.php
new file mode 100644
index 00000000000..51e734a774b
--- /dev/null
+++ b/apps/testing/composer/composer/InstalledVersions.php
@@ -0,0 +1,359 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list<string>
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list<string>
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+
+ if (self::$canGetVendors) {
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ $installed[] = self::$installedByVendor[$vendorDir] = $required;
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+ self::$installed = $installed[count($installed) - 1];
+ }
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array()) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
diff --git a/apps/testing/composer/composer/LICENSE b/apps/testing/composer/composer/LICENSE
new file mode 100644
index 00000000000..f27399a042d
--- /dev/null
+++ b/apps/testing/composer/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/apps/testing/composer/composer/autoload_classmap.php b/apps/testing/composer/composer/autoload_classmap.php
new file mode 100644
index 00000000000..9be96aaf617
--- /dev/null
+++ b/apps/testing/composer/composer/autoload_classmap.php
@@ -0,0 +1,33 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = $vendorDir;
+
+return array(
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+ 'OCA\\Testing\\AlternativeHomeUserBackend' => $baseDir . '/../lib/AlternativeHomeUserBackend.php',
+ 'OCA\\Testing\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
+ 'OCA\\Testing\\Controller\\ConfigController' => $baseDir . '/../lib/Controller/ConfigController.php',
+ 'OCA\\Testing\\Controller\\LockingController' => $baseDir . '/../lib/Controller/LockingController.php',
+ 'OCA\\Testing\\Controller\\RateLimitTestController' => $baseDir . '/../lib/Controller/RateLimitTestController.php',
+ 'OCA\\Testing\\Conversion\\ConversionProvider' => $baseDir . '/../lib/Conversion/ConversionProvider.php',
+ 'OCA\\Testing\\HiddenGroupBackend' => $baseDir . '/../lib/HiddenGroupBackend.php',
+ 'OCA\\Testing\\Listener\\GetDeclarativeSettingsValueListener' => $baseDir . '/../lib/Listener/GetDeclarativeSettingsValueListener.php',
+ 'OCA\\Testing\\Listener\\RegisterDeclarativeSettingsListener' => $baseDir . '/../lib/Listener/RegisterDeclarativeSettingsListener.php',
+ 'OCA\\Testing\\Listener\\SetDeclarativeSettingsValueListener' => $baseDir . '/../lib/Listener/SetDeclarativeSettingsValueListener.php',
+ 'OCA\\Testing\\Locking\\FakeDBLockingProvider' => $baseDir . '/../lib/Locking/FakeDBLockingProvider.php',
+ 'OCA\\Testing\\Migration\\Version30000Date20240102030405' => $baseDir . '/../lib/Migration/Version30000Date20240102030405.php',
+ 'OCA\\Testing\\Provider\\FakeText2ImageProvider' => $baseDir . '/../lib/Provider/FakeText2ImageProvider.php',
+ 'OCA\\Testing\\Provider\\FakeTextProcessingProvider' => $baseDir . '/../lib/Provider/FakeTextProcessingProvider.php',
+ 'OCA\\Testing\\Provider\\FakeTextProcessingProviderSync' => $baseDir . '/../lib/Provider/FakeTextProcessingProviderSync.php',
+ 'OCA\\Testing\\Provider\\FakeTranslationProvider' => $baseDir . '/../lib/Provider/FakeTranslationProvider.php',
+ 'OCA\\Testing\\Settings\\DeclarativeSettingsForm' => $baseDir . '/../lib/Settings/DeclarativeSettingsForm.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeContextWriteProvider' => $baseDir . '/../lib/TaskProcessing/FakeContextWriteProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTextToImageProvider' => $baseDir . '/../lib/TaskProcessing/FakeTextToImageProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTextToTextProvider' => $baseDir . '/../lib/TaskProcessing/FakeTextToTextProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTextToTextSummaryProvider' => $baseDir . '/../lib/TaskProcessing/FakeTextToTextSummaryProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTranscribeProvider' => $baseDir . '/../lib/TaskProcessing/FakeTranscribeProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTranslateProvider' => $baseDir . '/../lib/TaskProcessing/FakeTranslateProvider.php',
+);
diff --git a/apps/testing/composer/composer/autoload_namespaces.php b/apps/testing/composer/composer/autoload_namespaces.php
new file mode 100644
index 00000000000..3f5c9296251
--- /dev/null
+++ b/apps/testing/composer/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = $vendorDir;
+
+return array(
+);
diff --git a/apps/testing/composer/composer/autoload_psr4.php b/apps/testing/composer/composer/autoload_psr4.php
new file mode 100644
index 00000000000..416052e6c12
--- /dev/null
+++ b/apps/testing/composer/composer/autoload_psr4.php
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = $vendorDir;
+
+return array(
+ 'OCA\\Testing\\' => array($baseDir . '/../lib'),
+);
diff --git a/apps/testing/composer/composer/autoload_real.php b/apps/testing/composer/composer/autoload_real.php
new file mode 100644
index 00000000000..0d4fabd66a0
--- /dev/null
+++ b/apps/testing/composer/composer/autoload_real.php
@@ -0,0 +1,37 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInitTesting
+{
+ private static $loader;
+
+ public static function loadClassLoader($class)
+ {
+ if ('Composer\Autoload\ClassLoader' === $class) {
+ require __DIR__ . '/ClassLoader.php';
+ }
+ }
+
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
+ public static function getLoader()
+ {
+ if (null !== self::$loader) {
+ return self::$loader;
+ }
+
+ spl_autoload_register(array('ComposerAutoloaderInitTesting', 'loadClassLoader'), true, true);
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
+ spl_autoload_unregister(array('ComposerAutoloaderInitTesting', 'loadClassLoader'));
+
+ require __DIR__ . '/autoload_static.php';
+ call_user_func(\Composer\Autoload\ComposerStaticInitTesting::getInitializer($loader));
+
+ $loader->setClassMapAuthoritative(true);
+ $loader->register(true);
+
+ return $loader;
+ }
+}
diff --git a/apps/testing/composer/composer/autoload_static.php b/apps/testing/composer/composer/autoload_static.php
new file mode 100644
index 00000000000..bd557c37f6b
--- /dev/null
+++ b/apps/testing/composer/composer/autoload_static.php
@@ -0,0 +1,59 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInitTesting
+{
+ public static $prefixLengthsPsr4 = array (
+ 'O' =>
+ array (
+ 'OCA\\Testing\\' => 12,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'OCA\\Testing\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/../lib',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ 'OCA\\Testing\\AlternativeHomeUserBackend' => __DIR__ . '/..' . '/../lib/AlternativeHomeUserBackend.php',
+ 'OCA\\Testing\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
+ 'OCA\\Testing\\Controller\\ConfigController' => __DIR__ . '/..' . '/../lib/Controller/ConfigController.php',
+ 'OCA\\Testing\\Controller\\LockingController' => __DIR__ . '/..' . '/../lib/Controller/LockingController.php',
+ 'OCA\\Testing\\Controller\\RateLimitTestController' => __DIR__ . '/..' . '/../lib/Controller/RateLimitTestController.php',
+ 'OCA\\Testing\\Conversion\\ConversionProvider' => __DIR__ . '/..' . '/../lib/Conversion/ConversionProvider.php',
+ 'OCA\\Testing\\HiddenGroupBackend' => __DIR__ . '/..' . '/../lib/HiddenGroupBackend.php',
+ 'OCA\\Testing\\Listener\\GetDeclarativeSettingsValueListener' => __DIR__ . '/..' . '/../lib/Listener/GetDeclarativeSettingsValueListener.php',
+ 'OCA\\Testing\\Listener\\RegisterDeclarativeSettingsListener' => __DIR__ . '/..' . '/../lib/Listener/RegisterDeclarativeSettingsListener.php',
+ 'OCA\\Testing\\Listener\\SetDeclarativeSettingsValueListener' => __DIR__ . '/..' . '/../lib/Listener/SetDeclarativeSettingsValueListener.php',
+ 'OCA\\Testing\\Locking\\FakeDBLockingProvider' => __DIR__ . '/..' . '/../lib/Locking/FakeDBLockingProvider.php',
+ 'OCA\\Testing\\Migration\\Version30000Date20240102030405' => __DIR__ . '/..' . '/../lib/Migration/Version30000Date20240102030405.php',
+ 'OCA\\Testing\\Provider\\FakeText2ImageProvider' => __DIR__ . '/..' . '/../lib/Provider/FakeText2ImageProvider.php',
+ 'OCA\\Testing\\Provider\\FakeTextProcessingProvider' => __DIR__ . '/..' . '/../lib/Provider/FakeTextProcessingProvider.php',
+ 'OCA\\Testing\\Provider\\FakeTextProcessingProviderSync' => __DIR__ . '/..' . '/../lib/Provider/FakeTextProcessingProviderSync.php',
+ 'OCA\\Testing\\Provider\\FakeTranslationProvider' => __DIR__ . '/..' . '/../lib/Provider/FakeTranslationProvider.php',
+ 'OCA\\Testing\\Settings\\DeclarativeSettingsForm' => __DIR__ . '/..' . '/../lib/Settings/DeclarativeSettingsForm.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeContextWriteProvider' => __DIR__ . '/..' . '/../lib/TaskProcessing/FakeContextWriteProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTextToImageProvider' => __DIR__ . '/..' . '/../lib/TaskProcessing/FakeTextToImageProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTextToTextProvider' => __DIR__ . '/..' . '/../lib/TaskProcessing/FakeTextToTextProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTextToTextSummaryProvider' => __DIR__ . '/..' . '/../lib/TaskProcessing/FakeTextToTextSummaryProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTranscribeProvider' => __DIR__ . '/..' . '/../lib/TaskProcessing/FakeTranscribeProvider.php',
+ 'OCA\\Testing\\TaskProcessing\\FakeTranslateProvider' => __DIR__ . '/..' . '/../lib/TaskProcessing/FakeTranslateProvider.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitTesting::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitTesting::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitTesting::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/apps/testing/composer/composer/installed.json b/apps/testing/composer/composer/installed.json
new file mode 100644
index 00000000000..f20a6c47c6d
--- /dev/null
+++ b/apps/testing/composer/composer/installed.json
@@ -0,0 +1,5 @@
+{
+ "packages": [],
+ "dev": false,
+ "dev-package-names": []
+}
diff --git a/apps/testing/composer/composer/installed.php b/apps/testing/composer/composer/installed.php
new file mode 100644
index 00000000000..d2b87e1bdfd
--- /dev/null
+++ b/apps/testing/composer/composer/installed.php
@@ -0,0 +1,23 @@
+<?php return array(
+ 'root' => array(
+ 'name' => '__root__',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '4ff660ca2e0baa02440ba07296ed7e75fa544c0e',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../',
+ 'aliases' => array(),
+ 'dev' => false,
+ ),
+ 'versions' => array(
+ '__root__' => array(
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '4ff660ca2e0baa02440ba07296ed7e75fa544c0e',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/apps/testing/config.php b/apps/testing/config.php
deleted file mode 100644
index d1cb021581d..00000000000
--- a/apps/testing/config.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-/**
- * @author Joas Schilling <nickvergessen@owncloud.com>
- *
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-namespace OCA\Testing;
-
-use OCP\IConfig;
-use OCP\IRequest;
-
-class Config {
-
- /** @var IConfig */
- private $config;
-
- /** @var IRequest */
- private $request;
-
- /**
- * @param IConfig $config
- * @param IRequest $request
- */
- public function __construct(IConfig $config, IRequest $request) {
- $this->config = $config;
- $this->request = $request;
- }
-
- /**
- * @param array $parameters
- * @return \OC_OCS_Result
- */
- public function setAppValue($parameters) {
- $app = $parameters['appid'];
- $configKey = $parameters['configkey'];
-
- $value = $this->request->getParam('value');
- $this->config->setAppValue($app, $configKey, $value);
-
- return new \OC_OCS_Result();
- }
-
- /**
- * @param array $parameters
- * @return \OC_OCS_Result
- */
- public function deleteAppValue($parameters) {
- $app = $parameters['appid'];
- $configKey = $parameters['configkey'];
-
- $this->config->deleteAppValue($app, $configKey);
-
- return new \OC_OCS_Result();
- }
-}
diff --git a/apps/testing/img/app.svg b/apps/testing/img/app.svg
index b6ae35211a3..078e67202b6 100644
--- a/apps/testing/img/app.svg
+++ b/apps/testing/img/app.svg
@@ -1,4 +1 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
- <path d="m13.733 0.00064c-0.52991 0-0.93331 0.40337-0.93331 0.93333v2.6666c-1.182 0.3034-2.243 0.7934-3.2668 1.4001l-1.9334-1.9334c-0.3747-0.3747-0.9586-0.3747-1.3333 0l-3.1999 3.2c-0.37473 0.37474-0.37473 0.95859 0 1.3333l1.9334 1.9335c-0.6067 1.0239-1.0967 2.0849-1.4001 3.2669h-2.6666c-0.52994 0-0.9333 0.403-0.9333 0.933v4.5333c2e-8 0.52996 0.40336 0.93333 0.93331 0.93333h2.6666c0.30335 1.1817 0.79332 2.2426 1.4 3.2666l-1.9334 1.9349c-0.37473 0.37474-0.37473 0.95859 0 1.3333l3.1999 3.2c0.37473 0.37474 0.95857 0.37474 1.3333 0l1.9334-1.9349c1.024 0.608 2.0849 1.0965 3.2665 1.3995v2.6667c0 0.53 0.403 0.933 0.933 0.933h4.5332c0.52991 0 0.93331-0.4032 0.93331-0.9344v-2.6667c1.1816-0.30336 2.2425-0.79335 3.2665-1.4l1.9333 1.9333c0.37473 0.37474 0.95857 0.37474 1.3333 0l3.1999-3.2c0.37473-0.37474 0.37473-0.95859 0-1.3333l-1.9327-1.9328c0.60798-1.024 1.0965-2.0845 1.3994-3.2661h2.6666c0.532 0 0.935-0.403 0.935-0.933v-4.534c0-0.53-0.403-0.933-0.934-0.933h-2.667c-0.303-1.182-0.791-2.243-1.399-3.2666l1.932-1.9334c0.37473-0.37474 0.37473-0.95859 0-1.3333l-3.2-3.2c-0.37473-0.37474-0.95857-0.37474-1.3333 0l-1.9327 1.9334c-1.024-0.6067-2.084-1.0967-3.266-1.4001v-2.6667c0-0.52993-0.403-0.9333-0.933-0.9333zm2.2666 8.8689c3.9361 0 7.1309 3.1947 7.1309 7.1311 0 3.9362-3.1946 7.1311-7.1309 7.1311-3.9361 0-7.1309-3.1955-7.1309-7.1317s3.1948-7.1311 7.1309-7.1311z" display="block" fill="#fff"/>
-</svg>
+<svg viewBox="0 0 32 32" height="32" width="32" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><path d="m9.25 22-.4-3.2a3.697 3.697 0 0 1-.612-.3 8.336 8.336 0 0 1-.563-.375L4.7 19.375l-2.75-4.75 2.575-1.95a2.342 2.342 0 0 1-.025-.338v-.674c0-.109.008-.221.025-.338L1.95 9.375l2.75-4.75 2.975 1.25c.183-.133.375-.258.575-.375.2-.117.4-.217.6-.3l.4-3.2h5.5l.4 3.2c.217.083.421.183.613.3.191.117.379.242.562.375l2.975-1.25 2.75 4.75-2.575 1.95c.017.117.025.229.025.338v.674c0 .109-.017.221-.05.338l2.575 1.95-2.75 4.75-2.95-1.25a6.928 6.928 0 0 1-.575.375c-.2.117-.4.217-.6.3l-.4 3.2h-5.5Zm2.8-6.5c.967 0 1.792-.342 2.475-1.025A3.373 3.373 0 0 0 15.55 12c0-.967-.342-1.792-1.025-2.475A3.373 3.373 0 0 0 12.05 8.5c-.983 0-1.812.342-2.487 1.025A3.393 3.393 0 0 0 8.55 12c0 .967.338 1.792 1.013 2.475.675.683 1.504 1.025 2.487 1.025Z" style="fill:#fff;fill-rule:nonzero" transform="matrix(1.59204 0 0 1.6 -3.104 -3.2)"/></svg> \ No newline at end of file
diff --git a/apps/testing/img/logo.png b/apps/testing/img/logo.png
new file mode 100644
index 00000000000..df32e1c7eab
--- /dev/null
+++ b/apps/testing/img/logo.png
Binary files differ
diff --git a/apps/testing/lib/AlternativeHomeUserBackend.php b/apps/testing/lib/AlternativeHomeUserBackend.php
new file mode 100644
index 00000000000..0524ebe110d
--- /dev/null
+++ b/apps/testing/lib/AlternativeHomeUserBackend.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 ownCloud GmbH.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCA\Testing;
+
+use OC\User\Database;
+use OCP\IConfig;
+use OCP\Server;
+
+/**
+ * Alternative home user backend.
+ *
+ * It returns a md5 of the home folder instead of the user id.
+ * To configure, need to add this in config.php:
+ * 'user_backends' => [
+ * 'default' => false, [
+ * 'class' => '\\OCA\\Testing\\AlternativeHomeUserBackend',
+ * 'arguments' => [],
+ * ],
+ * ]
+ */
+class AlternativeHomeUserBackend extends Database {
+ public function __construct() {
+ parent::__construct();
+ }
+ /**
+ * get the user's home directory
+ * @param string $uid the username
+ * @return string|false
+ */
+ public function getHome($uid) {
+ if ($this->userExists($uid)) {
+ // workaround to avoid killing the admin
+ if ($uid !== 'admin') {
+ $uid = md5($uid);
+ }
+ return Server::get(IConfig::class)->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $uid;
+ }
+
+ return false;
+ }
+}
diff --git a/apps/testing/lib/AppInfo/Application.php b/apps/testing/lib/AppInfo/Application.php
new file mode 100644
index 00000000000..2add318f327
--- /dev/null
+++ b/apps/testing/lib/AppInfo/Application.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCA\Testing\AppInfo;
+
+use OCA\Testing\AlternativeHomeUserBackend;
+use OCA\Testing\Conversion\ConversionProvider;
+use OCA\Testing\HiddenGroupBackend;
+use OCA\Testing\Listener\GetDeclarativeSettingsValueListener;
+use OCA\Testing\Listener\RegisterDeclarativeSettingsListener;
+use OCA\Testing\Listener\SetDeclarativeSettingsValueListener;
+use OCA\Testing\Provider\FakeText2ImageProvider;
+use OCA\Testing\Provider\FakeTextProcessingProvider;
+use OCA\Testing\Provider\FakeTextProcessingProviderSync;
+use OCA\Testing\Provider\FakeTranslationProvider;
+use OCA\Testing\Settings\DeclarativeSettingsForm;
+use OCA\Testing\TaskProcessing\FakeContextWriteProvider;
+use OCA\Testing\TaskProcessing\FakeTextToImageProvider;
+use OCA\Testing\TaskProcessing\FakeTextToTextProvider;
+use OCA\Testing\TaskProcessing\FakeTextToTextSummaryProvider;
+use OCA\Testing\TaskProcessing\FakeTranscribeProvider;
+use OCA\Testing\TaskProcessing\FakeTranslateProvider;
+use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
+use OCP\IGroupManager;
+use OCP\Settings\Events\DeclarativeSettingsGetValueEvent;
+use OCP\Settings\Events\DeclarativeSettingsRegisterFormEvent;
+use OCP\Settings\Events\DeclarativeSettingsSetValueEvent;
+
+class Application extends App implements IBootstrap {
+ public const APP_ID = 'testing';
+
+ public function __construct(array $urlParams = []) {
+ parent::__construct(self::APP_ID, $urlParams);
+ }
+
+ public function register(IRegistrationContext $context): void {
+ $context->registerTranslationProvider(FakeTranslationProvider::class);
+ $context->registerTextProcessingProvider(FakeTextProcessingProvider::class);
+ $context->registerTextProcessingProvider(FakeTextProcessingProviderSync::class);
+ $context->registerTextToImageProvider(FakeText2ImageProvider::class);
+
+ $context->registerTaskProcessingProvider(FakeTextToTextProvider::class);
+ $context->registerTaskProcessingProvider(FakeTextToTextSummaryProvider::class);
+ $context->registerTaskProcessingProvider(FakeTextToImageProvider::class);
+ $context->registerTaskProcessingProvider(FakeTranslateProvider::class);
+ $context->registerTaskProcessingProvider(FakeTranscribeProvider::class);
+ $context->registerTaskProcessingProvider(FakeContextWriteProvider::class);
+
+ $context->registerFileConversionProvider(ConversionProvider::class);
+
+ $context->registerDeclarativeSettings(DeclarativeSettingsForm::class);
+ $context->registerEventListener(DeclarativeSettingsRegisterFormEvent::class, RegisterDeclarativeSettingsListener::class);
+ $context->registerEventListener(DeclarativeSettingsGetValueEvent::class, GetDeclarativeSettingsValueListener::class);
+ $context->registerEventListener(DeclarativeSettingsSetValueEvent::class, SetDeclarativeSettingsValueListener::class);
+ }
+
+ public function boot(IBootContext $context): void {
+ $server = $context->getServerContainer();
+ $config = $server->getConfig();
+ if ($config->getAppValue(self::APP_ID, 'enable_alt_user_backend', 'no') === 'yes') {
+ $userManager = $server->getUserManager();
+
+ // replace all user backends with this one
+ $userManager->clearBackends();
+ $userManager->registerBackend($context->getAppContainer()->get(AlternativeHomeUserBackend::class));
+ }
+
+ $groupManager = $server->get(IGroupManager::class);
+ $groupManager->addBackend($server->get(HiddenGroupBackend::class));
+ }
+}
diff --git a/apps/testing/lib/Controller/ConfigController.php b/apps/testing/lib/Controller/ConfigController.php
new file mode 100644
index 00000000000..1b38666e51c
--- /dev/null
+++ b/apps/testing/lib/Controller/ConfigController.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCA\Testing\Controller;
+
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
+use OCP\IConfig;
+use OCP\IRequest;
+
+class ConfigController extends OCSController {
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IConfig $config
+ */
+ public function __construct(
+ $appName,
+ IRequest $request,
+ private IConfig $config,
+ ) {
+ parent::__construct($appName, $request);
+ }
+
+ /**
+ * @param string $appid
+ * @param string $configkey
+ * @param string $value
+ * @return DataResponse
+ */
+ public function setAppValue($appid, $configkey, $value) {
+ $this->config->setAppValue($appid, $configkey, $value);
+ return new DataResponse();
+ }
+
+ /**
+ * @param string $appid
+ * @param string $configkey
+ * @return DataResponse
+ */
+ public function deleteAppValue($appid, $configkey) {
+ $this->config->deleteAppValue($appid, $configkey);
+ return new DataResponse();
+ }
+}
diff --git a/apps/testing/lib/Controller/LockingController.php b/apps/testing/lib/Controller/LockingController.php
new file mode 100644
index 00000000000..edc66d012ee
--- /dev/null
+++ b/apps/testing/lib/Controller/LockingController.php
@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCA\Testing\Controller;
+
+use OC\Lock\DBLockingProvider;
+use OC\User\NoUserException;
+use OCA\Testing\Locking\FakeDBLockingProvider;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCS\OCSException;
+use OCP\AppFramework\OCSController;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\IRequest;
+use OCP\Lock\ILockingProvider;
+use OCP\Lock\LockedException;
+
+class LockingController extends OCSController {
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param ILockingProvider $lockingProvider
+ * @param FakeDBLockingProvider $fakeDBLockingProvider
+ * @param IDBConnection $connection
+ * @param IConfig $config
+ * @param IRootFolder $rootFolder
+ */
+ public function __construct(
+ $appName,
+ IRequest $request,
+ protected ILockingProvider $lockingProvider,
+ protected FakeDBLockingProvider $fakeDBLockingProvider,
+ protected IDBConnection $connection,
+ protected IConfig $config,
+ protected IRootFolder $rootFolder,
+ ) {
+ parent::__construct($appName, $request);
+ }
+
+ /**
+ * @throws \RuntimeException
+ */
+ protected function getLockingProvider(): ILockingProvider {
+ if ($this->lockingProvider instanceof DBLockingProvider) {
+ return $this->fakeDBLockingProvider;
+ }
+ throw new \RuntimeException('Lock provisioning is only possible using the DBLockingProvider');
+ }
+
+ /**
+ * @throws NotFoundException
+ */
+ protected function getPath(string $user, string $path): string {
+ $node = $this->rootFolder->getUserFolder($user)->get($path);
+ return 'files/' . md5($node->getStorage()->getId() . '::' . trim($node->getInternalPath(), '/'));
+ }
+
+ /**
+ * @throws OCSException
+ */
+ public function isLockingEnabled(): DataResponse {
+ try {
+ $this->getLockingProvider();
+ return new DataResponse();
+ } catch (\RuntimeException $e) {
+ throw new OCSException($e->getMessage(), Http::STATUS_NOT_IMPLEMENTED, $e);
+ }
+ }
+
+ /**
+ * @throws OCSException
+ */
+ public function acquireLock(int $type, string $user, string $path): DataResponse {
+ try {
+ $path = $this->getPath($user, $path);
+ } catch (NoUserException $e) {
+ throw new OCSException('User not found', Http::STATUS_NOT_FOUND, $e);
+ } catch (NotFoundException $e) {
+ throw new OCSException('Path not found', Http::STATUS_NOT_FOUND, $e);
+ }
+
+ $lockingProvider = $this->getLockingProvider();
+
+ try {
+ $lockingProvider->acquireLock($path, $type);
+ $this->config->setAppValue('testing', 'locking_' . $path, (string)$type);
+ return new DataResponse();
+ } catch (LockedException $e) {
+ throw new OCSException('', Http::STATUS_LOCKED, $e);
+ }
+ }
+
+ /**
+ * @throws OCSException
+ */
+ public function changeLock(int $type, string $user, string $path): DataResponse {
+ try {
+ $path = $this->getPath($user, $path);
+ } catch (NoUserException $e) {
+ throw new OCSException('User not found', Http::STATUS_NOT_FOUND, $e);
+ } catch (NotFoundException $e) {
+ throw new OCSException('Path not found', Http::STATUS_NOT_FOUND, $e);
+ }
+
+ $lockingProvider = $this->getLockingProvider();
+
+ try {
+ $lockingProvider->changeLock($path, $type);
+ $this->config->setAppValue('testing', 'locking_' . $path, (string)$type);
+ return new DataResponse();
+ } catch (LockedException $e) {
+ throw new OCSException('', Http::STATUS_LOCKED, $e);
+ }
+ }
+
+ /**
+ * @throws OCSException
+ */
+ public function releaseLock(int $type, string $user, string $path): DataResponse {
+ try {
+ $path = $this->getPath($user, $path);
+ } catch (NoUserException $e) {
+ throw new OCSException('User not found', Http::STATUS_NOT_FOUND, $e);
+ } catch (NotFoundException $e) {
+ throw new OCSException('Path not found', Http::STATUS_NOT_FOUND, $e);
+ }
+
+ $lockingProvider = $this->getLockingProvider();
+
+ try {
+ $lockingProvider->releaseLock($path, $type);
+ $this->config->deleteAppValue('testing', 'locking_' . $path);
+ return new DataResponse();
+ } catch (LockedException $e) {
+ throw new OCSException('', Http::STATUS_LOCKED, $e);
+ }
+ }
+
+ /**
+ * @throws OCSException
+ */
+ public function isLocked(int $type, string $user, string $path): DataResponse {
+ try {
+ $path = $this->getPath($user, $path);
+ } catch (NoUserException $e) {
+ throw new OCSException('User not found', Http::STATUS_NOT_FOUND, $e);
+ } catch (NotFoundException $e) {
+ throw new OCSException('Path not found', Http::STATUS_NOT_FOUND, $e);
+ }
+
+ $lockingProvider = $this->getLockingProvider();
+
+ if ($lockingProvider->isLocked($path, $type)) {
+ return new DataResponse();
+ }
+
+ throw new OCSException('', Http::STATUS_LOCKED);
+ }
+
+ public function releaseAll(?int $type = null): DataResponse {
+ $lockingProvider = $this->getLockingProvider();
+
+ foreach ($this->config->getAppKeys('testing') as $lock) {
+ if (strpos($lock, 'locking_') === 0) {
+ $path = substr($lock, strlen('locking_'));
+
+ if ($type === ILockingProvider::LOCK_EXCLUSIVE && (int)$this->config->getAppValue('testing', $lock) === ILockingProvider::LOCK_EXCLUSIVE) {
+ $lockingProvider->releaseLock($path, (int)$this->config->getAppValue('testing', $lock));
+ } elseif ($type === ILockingProvider::LOCK_SHARED && (int)$this->config->getAppValue('testing', $lock) === ILockingProvider::LOCK_SHARED) {
+ $lockingProvider->releaseLock($path, (int)$this->config->getAppValue('testing', $lock));
+ } else {
+ $lockingProvider->releaseLock($path, (int)$this->config->getAppValue('testing', $lock));
+ }
+ }
+ }
+
+ return new DataResponse();
+ }
+}
diff --git a/apps/testing/lib/Controller/RateLimitTestController.php b/apps/testing/lib/Controller/RateLimitTestController.php
new file mode 100644
index 00000000000..d3700b69858
--- /dev/null
+++ b/apps/testing/lib/Controller/RateLimitTestController.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Testing\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\PublicPage;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
+use OCP\AppFramework\Http\JSONResponse;
+
+class RateLimitTestController extends Controller {
+ /**
+ * @return JSONResponse
+ */
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[UserRateLimit(limit: 5, period: 100)]
+ #[AnonRateLimit(limit: 1, period: 100)]
+ public function userAndAnonProtected() {
+ return new JSONResponse();
+ }
+
+ /**
+ * @return JSONResponse
+ */
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[AnonRateLimit(limit: 1, period: 10)]
+ public function onlyAnonProtected() {
+ return new JSONResponse();
+ }
+}
diff --git a/apps/testing/lib/Conversion/ConversionProvider.php b/apps/testing/lib/Conversion/ConversionProvider.php
new file mode 100644
index 00000000000..b8d93428694
--- /dev/null
+++ b/apps/testing/lib/Conversion/ConversionProvider.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Testing\Conversion;
+
+use OCP\Files\Conversion\ConversionMimeProvider;
+use OCP\Files\Conversion\IConversionProvider;
+use OCP\Files\File;
+use OCP\IL10N;
+
+class ConversionProvider implements IConversionProvider {
+ public function __construct(
+ private IL10N $l10n,
+ ) {
+ }
+
+ public function getSupportedMimeTypes(): array {
+ return [
+ new ConversionMimeProvider('image/jpeg', 'image/png', 'png', $this->l10n->t('Image (.png)')),
+ new ConversionMimeProvider('image/jpeg', 'image/gif', 'gif', $this->l10n->t('Image (.gif)')),
+ ];
+ }
+
+ public function convertFile(File $file, string $targetMimeType): mixed {
+ $image = imagecreatefromstring($file->getContent());
+ imagepalettetotruecolor($image);
+
+ // Start output buffering
+ ob_start();
+
+ // Convert the image to the target format
+ if ($targetMimeType === 'image/gif') {
+ imagegif($image);
+ } else {
+ imagepng($image);
+ }
+
+ // End and return the output buffer
+ return ob_get_clean();
+ }
+}
diff --git a/apps/testing/lib/HiddenGroupBackend.php b/apps/testing/lib/HiddenGroupBackend.php
new file mode 100644
index 00000000000..96ead46c06e
--- /dev/null
+++ b/apps/testing/lib/HiddenGroupBackend.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Testing;
+
+use OCP\Group\Backend\ABackend;
+use OCP\Group\Backend\IHideFromCollaborationBackend;
+
+class HiddenGroupBackend extends ABackend implements IHideFromCollaborationBackend {
+ public function __construct(
+ private string $groupName = 'hidden_group',
+ ) {
+ }
+
+ public function inGroup($uid, $gid): bool {
+ return false;
+ }
+
+ public function getUserGroups($uid): array {
+ return [];
+ }
+
+ public function getGroups($search = '', $limit = -1, $offset = 0): array {
+ return $offset === 0 ? [$this->groupName] : [];
+ }
+
+ public function groupExists($gid): bool {
+ return $gid === $this->groupName;
+ }
+
+ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0): array {
+ return [];
+ }
+
+ public function hideGroup(string $groupId): bool {
+ return true;
+ }
+}
diff --git a/apps/testing/lib/Listener/GetDeclarativeSettingsValueListener.php b/apps/testing/lib/Listener/GetDeclarativeSettingsValueListener.php
new file mode 100644
index 00000000000..0df58168007
--- /dev/null
+++ b/apps/testing/lib/Listener/GetDeclarativeSettingsValueListener.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Testing\Listener;
+
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\IConfig;
+use OCP\Settings\Events\DeclarativeSettingsGetValueEvent;
+
+/**
+ * @template-implements IEventListener<DeclarativeSettingsGetValueEvent>
+ */
+class GetDeclarativeSettingsValueListener implements IEventListener {
+
+ public function __construct(
+ private IConfig $config,
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!$event instanceof DeclarativeSettingsGetValueEvent) {
+ return;
+ }
+
+ if ($event->getApp() !== 'testing') {
+ return;
+ }
+
+ $value = $this->config->getUserValue($event->getUser()->getUID(), $event->getApp(), $event->getFieldId());
+ $event->setValue($value);
+ }
+}
diff --git a/apps/testing/lib/Listener/RegisterDeclarativeSettingsListener.php b/apps/testing/lib/Listener/RegisterDeclarativeSettingsListener.php
new file mode 100644
index 00000000000..aab39b78d76
--- /dev/null
+++ b/apps/testing/lib/Listener/RegisterDeclarativeSettingsListener.php
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Testing\Listener;
+
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Settings\DeclarativeSettingsTypes;
+use OCP\Settings\Events\DeclarativeSettingsRegisterFormEvent;
+
+/**
+ * @template-implements IEventListener<DeclarativeSettingsRegisterFormEvent>
+ */
+class RegisterDeclarativeSettingsListener implements IEventListener {
+
+ public function __construct() {
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof DeclarativeSettingsRegisterFormEvent)) {
+ // Unrelated
+ return;
+ }
+
+ $event->registerSchema('testing', [
+ 'id' => 'test_declarative_form_event',
+ 'priority' => 20,
+ 'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN,
+ 'section_id' => 'additional',
+ 'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL,
+ 'title' => 'Test declarative settings event', // NcSettingsSection name
+ 'description' => 'This form is registered via the RegisterDeclarativeSettingsFormEvent', // NcSettingsSection description
+ 'fields' => [
+ [
+ 'id' => 'event_field_1',
+ 'title' => 'Why is 42 this answer to all questions?',
+ 'description' => 'Hint: It\'s not',
+ 'type' => DeclarativeSettingsTypes::TEXT,
+ 'placeholder' => 'Enter your answer',
+ 'default' => 'Because it is',
+ ],
+ [
+ 'id' => 'feature_rating',
+ 'title' => 'How would you rate this feature?',
+ 'description' => 'Your vote is not anonymous',
+ 'type' => DeclarativeSettingsTypes::RADIO, // radio, radio-button (NcCheckboxRadioSwitch button-variant)
+ 'label' => 'Select single toggle',
+ 'default' => '3',
+ 'options' => [
+ [
+ 'name' => 'Awesome', // NcCheckboxRadioSwitch display name
+ 'value' => '1' // NcCheckboxRadioSwitch value
+ ],
+ [
+ 'name' => 'Very awesome',
+ 'value' => '2'
+ ],
+ [
+ 'name' => 'Super awesome',
+ 'value' => '3'
+ ],
+ ],
+ ],
+ ],
+ ]);
+ }
+}
diff --git a/apps/testing/lib/Listener/SetDeclarativeSettingsValueListener.php b/apps/testing/lib/Listener/SetDeclarativeSettingsValueListener.php
new file mode 100644
index 00000000000..0058e7df43e
--- /dev/null
+++ b/apps/testing/lib/Listener/SetDeclarativeSettingsValueListener.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Testing\Listener;
+
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\IConfig;
+use OCP\Settings\Events\DeclarativeSettingsSetValueEvent;
+
+/**
+ * @template-implements IEventListener<DeclarativeSettingsSetValueEvent>
+ */
+class SetDeclarativeSettingsValueListener implements IEventListener {
+
+ public function __construct(
+ private IConfig $config,
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!$event instanceof DeclarativeSettingsSetValueEvent) {
+ return;
+ }
+
+ if ($event->getApp() !== 'testing') {
+ return;
+ }
+
+ error_log('Testing app wants to store ' . $event->getValue() . ' for field ' . $event->getFieldId() . ' for user ' . $event->getUser()->getUID());
+ $this->config->setUserValue($event->getUser()->getUID(), $event->getApp(), $event->getFieldId(), $event->getValue());
+ }
+}
diff --git a/apps/testing/lib/Locking/FakeDBLockingProvider.php b/apps/testing/lib/Locking/FakeDBLockingProvider.php
new file mode 100644
index 00000000000..f77bacc7a63
--- /dev/null
+++ b/apps/testing/lib/Locking/FakeDBLockingProvider.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCA\Testing\Locking;
+
+use OC\Lock\DBLockingProvider;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IDBConnection;
+
+class FakeDBLockingProvider extends DBLockingProvider {
+ // Lock for 10 hours just to be sure
+ public const TTL = 36000;
+
+ /**
+ * Need a new child, because parent::connection is private instead of protected...
+ */
+ protected IDBConnection $db;
+
+ public function __construct(
+ IDBConnection $connection,
+ ITimeFactory $timeFactory,
+ ) {
+ parent::__construct($connection, $timeFactory);
+ $this->db = $connection;
+ }
+
+ /** @inheritDoc */
+ public function releaseLock(string $path, int $type): void {
+ // we DONT keep shared locks till the end of the request
+ if ($type === self::LOCK_SHARED) {
+ $this->db->executeUpdate(
+ 'UPDATE `*PREFIX*file_locks` SET `lock` = 0 WHERE `key` = ? AND `lock` = 1',
+ [$path]
+ );
+ }
+
+ parent::releaseLock($path, $type);
+ }
+
+ public function __destruct() {
+ // Prevent cleaning up at the end of the live time.
+ // parent::__destruct();
+ }
+}
diff --git a/apps/testing/lib/Migration/Version30000Date20240102030405.php b/apps/testing/lib/Migration/Version30000Date20240102030405.php
new file mode 100644
index 00000000000..e7b6bdcd618
--- /dev/null
+++ b/apps/testing/lib/Migration/Version30000Date20240102030405.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Testing\Migration;
+
+use Closure;
+use OCP\Migration\Attributes\AddColumn;
+use OCP\Migration\Attributes\AddIndex;
+use OCP\Migration\Attributes\ColumnType;
+use OCP\Migration\Attributes\CreateTable;
+use OCP\Migration\Attributes\DropColumn;
+use OCP\Migration\Attributes\DropIndex;
+use OCP\Migration\Attributes\DropTable;
+use OCP\Migration\Attributes\IndexType;
+use OCP\Migration\Attributes\ModifyColumn;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+#[DropTable(table: 'old_table')]
+#[CreateTable(table: 'new_table', description: 'Table is used to store things, but also to get more things', notes: ['this is a notice', 'and another one, if really needed'])]
+#[AddColumn(table: 'my_table')]
+#[AddColumn(table: 'my_table', name: 'another_field')]
+#[AddColumn(table: 'other_table', name: 'last_one', type: ColumnType::DATE)]
+#[AddIndex(table: 'my_table')]
+#[AddIndex(table: 'my_table', type: IndexType::PRIMARY)]
+#[DropColumn(table: 'other_table')]
+#[DropColumn(table: 'other_table', name: 'old_column', description: 'field is not used anymore and replaced by \'last_one\'')]
+#[DropIndex(table: 'other_table')]
+#[ModifyColumn(table: 'other_table')]
+#[ModifyColumn(table: 'other_table', name: 'this_field')]
+#[ModifyColumn(table: 'other_table', name: 'this_field', type: ColumnType::BIGINT)]
+class Version30000Date20240102030405 extends SimpleMigrationStep {
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
+ return null;
+ }
+}
diff --git a/apps/testing/lib/Provider/FakeText2ImageProvider.php b/apps/testing/lib/Provider/FakeText2ImageProvider.php
new file mode 100644
index 00000000000..6b607f23347
--- /dev/null
+++ b/apps/testing/lib/Provider/FakeText2ImageProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Testing\Provider;
+
+use OCP\TextToImage\IProvider;
+
+class FakeText2ImageProvider implements IProvider {
+
+ public function getName(): string {
+ return 'Fake Text2Image provider';
+ }
+
+ public function generate(string $prompt, array $resources): void {
+ foreach ($resources as $resource) {
+ $read = fopen(__DIR__ . '/../../img/logo.png', 'r');
+ stream_copy_to_stream($read, $resource);
+ fclose($read);
+ }
+ }
+
+ public function getExpectedRuntime(): int {
+ return 1;
+ }
+
+ public function getId(): string {
+ return 'testing-fake-text2image-provider';
+ }
+}
diff --git a/apps/testing/lib/Provider/FakeTextProcessingProvider.php b/apps/testing/lib/Provider/FakeTextProcessingProvider.php
new file mode 100644
index 00000000000..d3b16c55c67
--- /dev/null
+++ b/apps/testing/lib/Provider/FakeTextProcessingProvider.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Testing\Provider;
+
+use OCP\TextProcessing\FreePromptTaskType;
+use OCP\TextProcessing\IProvider;
+use OCP\TextProcessing\ITaskType;
+
+/** @template-implements IProvider<FreePromptTaskType|ITaskType> */
+class FakeTextProcessingProvider implements IProvider {
+
+ public function getName(): string {
+ return 'Fake text processing provider (asynchronous)';
+ }
+
+ public function process(string $prompt): string {
+ return $this->mb_strrev($prompt) . ' (done with FakeTextProcessingProvider)';
+ }
+
+ public function getTaskType(): string {
+ return FreePromptTaskType::class;
+ }
+
+ /**
+ * Reverse a miltibyte string.
+ *
+ * @param string $string The string to be reversed.
+ * @return string The reversed string
+ */
+ private function mb_strrev(string $string): string {
+ $chars = mb_str_split($string, 1);
+ return implode('', array_reverse($chars));
+ }
+}
diff --git a/apps/testing/lib/Provider/FakeTextProcessingProviderSync.php b/apps/testing/lib/Provider/FakeTextProcessingProviderSync.php
new file mode 100644
index 00000000000..ea822199109
--- /dev/null
+++ b/apps/testing/lib/Provider/FakeTextProcessingProviderSync.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Testing\Provider;
+
+use OCP\TextProcessing\FreePromptTaskType;
+use OCP\TextProcessing\IProviderWithExpectedRuntime;
+use OCP\TextProcessing\ITaskType;
+
+/**
+ * @template-implements IProviderWithExpectedRuntime<FreePromptTaskType|ITaskType>
+ */
+class FakeTextProcessingProviderSync implements IProviderWithExpectedRuntime {
+
+ public function getName(): string {
+ return 'Fake text processing provider (synchronous)';
+ }
+
+ public function process(string $prompt): string {
+ return $this->mb_strrev($prompt) . ' (done with FakeTextProcessingProviderSync)';
+ }
+
+ public function getTaskType(): string {
+ return FreePromptTaskType::class;
+ }
+
+ public function getExpectedRuntime(): int {
+ return 1;
+ }
+
+ /**
+ * Reverse a miltibyte string.
+ *
+ * @param string $string The string to be reversed.
+ * @return string The reversed string
+ */
+ private function mb_strrev(string $string): string {
+ $chars = mb_str_split($string, 1);
+ return implode('', array_reverse($chars));
+ }
+}
diff --git a/apps/testing/lib/Provider/FakeTranslationProvider.php b/apps/testing/lib/Provider/FakeTranslationProvider.php
new file mode 100644
index 00000000000..cc2d13db646
--- /dev/null
+++ b/apps/testing/lib/Provider/FakeTranslationProvider.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Testing\Provider;
+
+use OCP\Translation\ITranslationProvider;
+use OCP\Translation\LanguageTuple;
+
+class FakeTranslationProvider implements ITranslationProvider {
+
+ public function getName(): string {
+ return 'Fake translation';
+ }
+
+ public function getAvailableLanguages(): array {
+ return [
+ new LanguageTuple('de', 'German', 'en', 'English'),
+ new LanguageTuple('en', 'English', 'de', 'German'),
+ ];
+ }
+
+ public function translate(?string $fromLanguage, string $toLanguage, string $text): string {
+ return strrev($text);
+ }
+}
diff --git a/apps/testing/lib/Settings/DeclarativeSettingsForm.php b/apps/testing/lib/Settings/DeclarativeSettingsForm.php
new file mode 100644
index 00000000000..55e44cbcbea
--- /dev/null
+++ b/apps/testing/lib/Settings/DeclarativeSettingsForm.php
@@ -0,0 +1,205 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Testing\Settings;
+
+use OCP\Settings\DeclarativeSettingsTypes;
+use OCP\Settings\IDeclarativeSettingsForm;
+
+class DeclarativeSettingsForm implements IDeclarativeSettingsForm {
+ public function getSchema(): array {
+ return [
+ 'id' => 'test_declarative_form',
+ 'priority' => 10,
+ 'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, // admin, personal
+ 'section_id' => 'additional',
+ 'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, // external, internal (handled by core to store in appconfig and preferences)
+ 'title' => 'Test declarative settings class', // NcSettingsSection name
+ 'description' => 'This form is registered with a DeclarativeSettingsForm class', // NcSettingsSection description
+ 'doc_url' => '', // NcSettingsSection doc_url for documentation or help page, empty string if not needed
+ 'fields' => [
+ [
+ 'id' => 'test_ex_app_field_7', // configkey
+ 'title' => 'Multi-selection', // name or label
+ 'description' => 'Select some option setting', // hint
+ 'type' => DeclarativeSettingsTypes::MULTI_SELECT, // select, radio, multi-select
+ 'options' => ['foo', 'bar', 'baz'], // simple options for select, radio, multi-select
+ 'placeholder' => 'Select some multiple options', // input placeholder
+ 'default' => ['foo', 'bar'],
+ ],
+ [
+ 'id' => 'some_real_setting',
+ 'title' => 'Choose init status check background job interval',
+ 'description' => 'How often AppAPI should check for initialization status',
+ 'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
+ 'placeholder' => 'Choose init status check background job interval',
+ 'default' => '40m',
+ 'options' => [
+ [
+ 'name' => 'Each 40 minutes', // NcCheckboxRadioSwitch display name
+ 'value' => '40m' // NcCheckboxRadioSwitch value
+ ],
+ [
+ 'name' => 'Each 60 minutes',
+ 'value' => '60m'
+ ],
+ [
+ 'name' => 'Each 120 minutes',
+ 'value' => '120m'
+ ],
+ [
+ 'name' => 'Each day',
+ 'value' => 60 * 24 . 'm'
+ ],
+ ],
+ ],
+ [
+ 'id' => 'test_ex_app_field_1', // configkey
+ 'title' => 'Default text field', // label
+ 'description' => 'Set some simple text setting', // hint
+ 'type' => DeclarativeSettingsTypes::TEXT, // text, password, email, tel, url, number
+ 'placeholder' => 'Enter text setting', // placeholder
+ 'default' => 'foo',
+ ],
+ [
+ 'id' => 'test_ex_app_field_1_1',
+ 'title' => 'Email field',
+ 'description' => 'Set email config',
+ 'type' => DeclarativeSettingsTypes::EMAIL,
+ 'placeholder' => 'Enter email',
+ 'default' => '',
+ ],
+ [
+ 'id' => 'test_ex_app_field_1_2',
+ 'title' => 'Tel field',
+ 'description' => 'Set tel config',
+ 'type' => DeclarativeSettingsTypes::TEL,
+ 'placeholder' => 'Enter your tel',
+ 'default' => '',
+ ],
+ [
+ 'id' => 'test_ex_app_field_1_3',
+ 'title' => 'Url (website) field',
+ 'description' => 'Set url config',
+ 'type' => 'url',
+ 'placeholder' => 'Enter url',
+ 'default' => '',
+ ],
+ [
+ 'id' => 'test_ex_app_field_1_4',
+ 'title' => 'Number field',
+ 'description' => 'Set number config',
+ 'type' => DeclarativeSettingsTypes::NUMBER,
+ 'placeholder' => 'Enter number value',
+ 'default' => 0,
+ ],
+ [
+ 'id' => 'test_ex_app_field_2',
+ 'title' => 'Password',
+ 'description' => 'Set some secure value setting',
+ 'type' => 'password',
+ 'placeholder' => 'Set secure value',
+ 'default' => '',
+ ],
+ [
+ 'id' => 'test_ex_app_field_3',
+ 'title' => 'Selection',
+ 'description' => 'Select some option setting',
+ 'type' => DeclarativeSettingsTypes::SELECT, // select, radio, multi-select
+ 'options' => ['foo', 'bar', 'baz'],
+ 'placeholder' => 'Select some option setting',
+ 'default' => 'foo',
+ ],
+ [
+ 'id' => 'test_ex_app_field_4',
+ 'title' => 'Toggle something',
+ 'description' => 'Select checkbox option setting',
+ 'type' => DeclarativeSettingsTypes::CHECKBOX, // checkbox, multiple-checkbox
+ 'label' => 'Verify something if enabled',
+ 'default' => false,
+ ],
+ [
+ 'id' => 'test_ex_app_field_5',
+ 'title' => 'Multiple checkbox toggles, describing one setting, checked options are saved as an JSON object {foo: true, bar: false}',
+ 'description' => 'Select checkbox option setting',
+ 'type' => DeclarativeSettingsTypes::MULTI_CHECKBOX, // checkbox, multi-checkbox
+ 'default' => ['foo' => true, 'bar' => true, 'baz' => true],
+ 'options' => [
+ [
+ 'name' => 'Foo',
+ 'value' => 'foo', // multiple-checkbox configkey
+ ],
+ [
+ 'name' => 'Bar',
+ 'value' => 'bar',
+ ],
+ [
+ 'name' => 'Baz',
+ 'value' => 'baz',
+ ],
+ [
+ 'name' => 'Qux',
+ 'value' => 'qux',
+ ],
+ ],
+ ],
+ [
+ 'id' => 'test_ex_app_field_6',
+ 'title' => 'Radio toggles, describing one setting like single select',
+ 'description' => 'Select radio option setting',
+ 'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
+ 'label' => 'Select single toggle',
+ 'default' => 'foo',
+ 'options' => [
+ [
+ 'name' => 'First radio', // NcCheckboxRadioSwitch display name
+ 'value' => 'foo' // NcCheckboxRadioSwitch value
+ ],
+ [
+ 'name' => 'Second radio',
+ 'value' => 'bar'
+ ],
+ [
+ 'name' => 'Third radio',
+ 'value' => 'baz'
+ ],
+ ],
+ ],
+ [
+ 'id' => 'test_sensitive_field',
+ 'title' => 'Sensitive text field',
+ 'description' => 'Set some secure value setting that is stored encrypted',
+ 'type' => DeclarativeSettingsTypes::TEXT,
+ 'label' => 'Sensitive field',
+ 'placeholder' => 'Set secure value',
+ 'default' => '',
+ 'sensitive' => true, // only for TEXT, PASSWORD types
+ ],
+ [
+ 'id' => 'test_sensitive_field_2',
+ 'title' => 'Sensitive password field',
+ 'description' => 'Set some password setting that is stored encrypted',
+ 'type' => DeclarativeSettingsTypes::PASSWORD,
+ 'label' => 'Sensitive field',
+ 'placeholder' => 'Set secure value',
+ 'default' => '',
+ 'sensitive' => true, // only for TEXT, PASSWORD types
+ ],
+ [
+ 'id' => 'test_non_sensitive_field',
+ 'title' => 'Password field',
+ 'description' => 'Set some password setting',
+ 'type' => DeclarativeSettingsTypes::PASSWORD,
+ 'label' => 'Password field',
+ 'placeholder' => 'Set secure value',
+ 'default' => '',
+ 'sensitive' => false,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/apps/testing/lib/TaskProcessing/FakeContextWriteProvider.php b/apps/testing/lib/TaskProcessing/FakeContextWriteProvider.php
new file mode 100644
index 00000000000..9a5574f5147
--- /dev/null
+++ b/apps/testing/lib/TaskProcessing/FakeContextWriteProvider.php
@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace OCA\Testing\TaskProcessing;
+
+use OCA\Testing\AppInfo\Application;
+use OCP\AppFramework\Services\IAppConfig;
+use OCP\TaskProcessing\EShapeType;
+use OCP\TaskProcessing\Exception\ProcessingException;
+use OCP\TaskProcessing\ISynchronousProvider;
+use OCP\TaskProcessing\ShapeDescriptor;
+use OCP\TaskProcessing\ShapeEnumValue;
+use OCP\TaskProcessing\TaskTypes\ContextWrite;
+use RuntimeException;
+
+class FakeContextWriteProvider implements ISynchronousProvider {
+
+ public function __construct(
+ protected IAppConfig $appConfig,
+ ) {
+ }
+
+ public function getId(): string {
+ return Application::APP_ID . '-contextwrite';
+ }
+
+ public function getName(): string {
+ return 'Fake context write task processing provider';
+ }
+
+ public function getTaskTypeId(): string {
+ return ContextWrite::ID;
+ }
+
+ public function getExpectedRuntime(): int {
+ return 1;
+ }
+
+ public function getInputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getInputShapeDefaults(): array {
+ return [];
+ }
+
+ public function getOptionalInputShape(): array {
+ return [
+ 'max_tokens' => new ShapeDescriptor(
+ 'Maximum output words',
+ 'The maximum number of words/tokens that can be generated in the completion.',
+ EShapeType::Number
+ ),
+ 'model' => new ShapeDescriptor(
+ 'Model',
+ 'The model used to generate the completion',
+ EShapeType::Enum
+ ),
+ ];
+ }
+
+ public function getOptionalInputShapeEnumValues(): array {
+ return [
+ 'model' => [
+ new ShapeEnumValue('Model 1', 'model_1'),
+ new ShapeEnumValue('Model 2', 'model_2'),
+ new ShapeEnumValue('Model 3', 'model_3'),
+ ],
+ ];
+ }
+
+ public function getOptionalInputShapeDefaults(): array {
+ return [
+ 'max_tokens' => 4321,
+ 'model' => 'model_2',
+ ];
+ }
+
+ public function getOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getOptionalOutputShape(): array {
+ return [];
+ }
+
+ public function getOptionalOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function process(?string $userId, array $input, callable $reportProgress): array {
+ if ($this->appConfig->getAppValueBool('fail-' . $this->getId())) {
+ throw new ProcessingException('Failing as set by AppConfig');
+ }
+
+ if (
+ !isset($input['style_input']) || !is_string($input['style_input'])
+ || !isset($input['source_input']) || !is_string($input['source_input'])
+ ) {
+ throw new RuntimeException('Invalid inputs');
+ }
+ $writingStyle = $input['style_input'];
+ $sourceMaterial = $input['source_input'];
+
+ if (isset($input['model']) && is_string($input['model'])) {
+ $model = $input['model'];
+ } else {
+ $model = 'unknown model';
+ }
+
+ $maxTokens = null;
+ if (isset($input['max_tokens']) && is_int($input['max_tokens'])) {
+ $maxTokens = $input['max_tokens'];
+ }
+
+ $fakeResult = 'This is a fake result: '
+ . "\n\n- Style input: " . $writingStyle
+ . "\n- Source input: " . $sourceMaterial
+ . "\n- Model: " . $model
+ . "\n- Maximum number of words: " . $maxTokens;
+
+ return ['output' => $fakeResult];
+ }
+}
diff --git a/apps/testing/lib/TaskProcessing/FakeTextToImageProvider.php b/apps/testing/lib/TaskProcessing/FakeTextToImageProvider.php
new file mode 100644
index 00000000000..455d6c0b518
--- /dev/null
+++ b/apps/testing/lib/TaskProcessing/FakeTextToImageProvider.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace OCA\Testing\TaskProcessing;
+
+use OCA\Testing\AppInfo\Application;
+use OCP\AppFramework\Services\IAppConfig;
+use OCP\TaskProcessing\EShapeType;
+use OCP\TaskProcessing\Exception\ProcessingException;
+use OCP\TaskProcessing\ISynchronousProvider;
+use OCP\TaskProcessing\ShapeDescriptor;
+use OCP\TaskProcessing\TaskTypes\TextToImage;
+use RuntimeException;
+
+class FakeTextToImageProvider implements ISynchronousProvider {
+
+ public function __construct(
+ protected IAppConfig $appConfig,
+ ) {
+ }
+
+ public function getId(): string {
+ return Application::APP_ID . '-text2image';
+ }
+
+ public function getName(): string {
+ return 'Fake text2image task processing provider';
+ }
+
+ public function getTaskTypeId(): string {
+ return TextToImage::ID;
+ }
+
+ public function getExpectedRuntime(): int {
+ return 1;
+ }
+
+ public function getInputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getInputShapeDefaults(): array {
+ return [
+ 'numberOfImages' => 1,
+ ];
+ }
+
+ public function getOptionalInputShape(): array {
+ return [
+ 'size' => new ShapeDescriptor(
+ 'Size',
+ 'Optional. The size of the generated images. Must be in 256x256 format.',
+ EShapeType::Text
+ ),
+ ];
+ }
+
+ public function getOptionalInputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getOptionalInputShapeDefaults(): array {
+ return [];
+ }
+
+ public function getOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getOptionalOutputShape(): array {
+ return [];
+ }
+
+ public function getOptionalOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function process(?string $userId, array $input, callable $reportProgress): array {
+ if ($this->appConfig->getAppValueBool('fail-' . $this->getId())) {
+ throw new ProcessingException('Failing as set by AppConfig');
+ }
+
+ if (!isset($input['input']) || !is_string($input['input'])) {
+ throw new RuntimeException('Invalid prompt');
+ }
+ $prompt = $input['input'];
+
+ $nbImages = 1;
+ if (isset($input['numberOfImages']) && is_int($input['numberOfImages'])) {
+ $nbImages = $input['numberOfImages'];
+ }
+
+ $fakeContent = file_get_contents(__DIR__ . '/../../img/logo.png');
+
+ $output = ['images' => []];
+ foreach (range(1, $nbImages) as $i) {
+ $output['images'][] = $fakeContent;
+ }
+ /** @var array<string, list<numeric|string>|numeric|string> $output */
+ return $output;
+ }
+}
diff --git a/apps/testing/lib/TaskProcessing/FakeTextToTextProvider.php b/apps/testing/lib/TaskProcessing/FakeTextToTextProvider.php
new file mode 100644
index 00000000000..5012823024e
--- /dev/null
+++ b/apps/testing/lib/TaskProcessing/FakeTextToTextProvider.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace OCA\Testing\TaskProcessing;
+
+use OCA\Testing\AppInfo\Application;
+use OCP\AppFramework\Services\IAppConfig;
+use OCP\TaskProcessing\EShapeType;
+use OCP\TaskProcessing\Exception\ProcessingException;
+use OCP\TaskProcessing\ISynchronousProvider;
+use OCP\TaskProcessing\ShapeDescriptor;
+use OCP\TaskProcessing\ShapeEnumValue;
+use OCP\TaskProcessing\TaskTypes\TextToText;
+use RuntimeException;
+
+class FakeTextToTextProvider implements ISynchronousProvider {
+
+ public function __construct(
+ protected IAppConfig $appConfig,
+ ) {
+ }
+
+ public function getId(): string {
+ return Application::APP_ID . '-text2text';
+ }
+
+ public function getName(): string {
+ return 'Fake text2text task processing provider';
+ }
+
+ public function getTaskTypeId(): string {
+ return TextToText::ID;
+ }
+
+ public function getExpectedRuntime(): int {
+ return 1;
+ }
+
+ public function getInputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getInputShapeDefaults(): array {
+ return [];
+ }
+
+ public function getOptionalInputShape(): array {
+ return [
+ 'max_tokens' => new ShapeDescriptor(
+ 'Maximum output words',
+ 'The maximum number of words/tokens that can be generated in the completion.',
+ EShapeType::Number
+ ),
+ 'model' => new ShapeDescriptor(
+ 'Model',
+ 'The model used to generate the completion',
+ EShapeType::Enum
+ ),
+ ];
+ }
+
+ public function getOptionalInputShapeEnumValues(): array {
+ return [
+ 'model' => [
+ new ShapeEnumValue('Model 1', 'model_1'),
+ new ShapeEnumValue('Model 2', 'model_2'),
+ new ShapeEnumValue('Model 3', 'model_3'),
+ ],
+ ];
+ }
+
+ public function getOptionalInputShapeDefaults(): array {
+ return [
+ 'max_tokens' => 1234,
+ 'model' => 'model_2',
+ ];
+ }
+
+ public function getOptionalOutputShape(): array {
+ return [];
+ }
+
+ public function getOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getOptionalOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function process(?string $userId, array $input, callable $reportProgress): array {
+ if ($this->appConfig->getAppValueBool('fail-' . $this->getId())) {
+ throw new ProcessingException('Failing as set by AppConfig');
+ }
+
+ if (isset($input['model']) && is_string($input['model'])) {
+ $model = $input['model'];
+ } else {
+ $model = 'unknown model';
+ }
+
+ if (!isset($input['input']) || !is_string($input['input'])) {
+ throw new RuntimeException('Invalid prompt');
+ }
+ $prompt = $input['input'];
+
+ $maxTokens = null;
+ if (isset($input['max_tokens']) && is_int($input['max_tokens'])) {
+ $maxTokens = $input['max_tokens'];
+ }
+
+ return [
+ 'output' => 'This is a fake result: ' . "\n\n- Prompt: " . $prompt . "\n- Model: " . $model . "\n- Maximum number of words: " . $maxTokens,
+ ];
+ }
+}
diff --git a/apps/testing/lib/TaskProcessing/FakeTextToTextSummaryProvider.php b/apps/testing/lib/TaskProcessing/FakeTextToTextSummaryProvider.php
new file mode 100644
index 00000000000..58816d8a0df
--- /dev/null
+++ b/apps/testing/lib/TaskProcessing/FakeTextToTextSummaryProvider.php
@@ -0,0 +1,122 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+
+namespace OCA\Testing\TaskProcessing;
+
+use OCA\Testing\AppInfo\Application;
+use OCP\AppFramework\Services\IAppConfig;
+use OCP\TaskProcessing\EShapeType;
+use OCP\TaskProcessing\Exception\ProcessingException;
+use OCP\TaskProcessing\ISynchronousProvider;
+use OCP\TaskProcessing\ShapeDescriptor;
+use OCP\TaskProcessing\ShapeEnumValue;
+use OCP\TaskProcessing\TaskTypes\TextToTextSummary;
+use RuntimeException;
+
+class FakeTextToTextSummaryProvider implements ISynchronousProvider {
+
+ public function __construct(
+ protected IAppConfig $appConfig,
+ ) {
+ }
+
+ public function getId(): string {
+ return Application::APP_ID . '-text2text-summary';
+ }
+
+ public function getName(): string {
+ return 'Fake text2text summary task processing provider';
+ }
+
+ public function getTaskTypeId(): string {
+ return TextToTextSummary::ID;
+ }
+
+ public function getExpectedRuntime(): int {
+ return 1;
+ }
+
+ public function getInputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getInputShapeDefaults(): array {
+ return [];
+ }
+
+ public function getOptionalInputShape(): array {
+ return [
+ 'max_tokens' => new ShapeDescriptor(
+ 'Maximum output words',
+ 'The maximum number of words/tokens that can be generated in the completion.',
+ EShapeType::Number
+ ),
+ 'model' => new ShapeDescriptor(
+ 'Model',
+ 'The model used to generate the completion',
+ EShapeType::Enum
+ ),
+ ];
+ }
+
+ public function getOptionalInputShapeEnumValues(): array {
+ return [
+ 'model' => [
+ new ShapeEnumValue('Model 1', 'model_1'),
+ new ShapeEnumValue('Model 2', 'model_2'),
+ new ShapeEnumValue('Model 3', 'model_3'),
+ ],
+ ];
+ }
+
+ public function getOptionalInputShapeDefaults(): array {
+ return [
+ 'max_tokens' => 1234,
+ 'model' => 'model_2',
+ ];
+ }
+
+ public function getOptionalOutputShape(): array {
+ return [];
+ }
+
+ public function getOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getOptionalOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function process(?string $userId, array $input, callable $reportProgress): array {
+ if ($this->appConfig->getAppValueBool('fail-' . $this->getId())) {
+ throw new ProcessingException('Failing as set by AppConfig');
+ }
+
+ if (isset($input['model']) && is_string($input['model'])) {
+ $model = $input['model'];
+ } else {
+ $model = 'unknown model';
+ }
+
+ if (!isset($input['input']) || !is_string($input['input'])) {
+ throw new RuntimeException('Invalid prompt');
+ }
+ $prompt = $input['input'];
+
+ $maxTokens = null;
+ if (isset($input['max_tokens']) && is_int($input['max_tokens'])) {
+ $maxTokens = $input['max_tokens'];
+ }
+
+ return [
+ 'output' => 'This is a fake summary: ',// . "\n\n- Prompt: " . $prompt . "\n- Model: " . $model . "\n- Maximum number of words: " . $maxTokens,
+ ];
+ }
+}
diff --git a/apps/testing/lib/TaskProcessing/FakeTranscribeProvider.php b/apps/testing/lib/TaskProcessing/FakeTranscribeProvider.php
new file mode 100644
index 00000000000..4827a07037a
--- /dev/null
+++ b/apps/testing/lib/TaskProcessing/FakeTranscribeProvider.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace OCA\Testing\TaskProcessing;
+
+use OCA\Testing\AppInfo\Application;
+use OCP\AppFramework\Services\IAppConfig;
+use OCP\Files\File;
+use OCP\TaskProcessing\Exception\ProcessingException;
+use OCP\TaskProcessing\ISynchronousProvider;
+use OCP\TaskProcessing\TaskTypes\AudioToText;
+use RuntimeException;
+
+class FakeTranscribeProvider implements ISynchronousProvider {
+
+ public function __construct(
+ protected IAppConfig $appConfig,
+ ) {
+ }
+
+ public function getId(): string {
+ return Application::APP_ID . '-audio2text';
+ }
+
+ public function getName(): string {
+ return 'Fake audio2text task processing provider';
+ }
+
+ public function getTaskTypeId(): string {
+ return AudioToText::ID;
+ }
+
+ public function getExpectedRuntime(): int {
+ return 1;
+ }
+
+ public function getInputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getInputShapeDefaults(): array {
+ return [];
+ }
+
+ public function getOptionalInputShape(): array {
+ return [];
+ }
+
+ public function getOptionalInputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getOptionalInputShapeDefaults(): array {
+ return [];
+ }
+
+ public function getOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getOptionalOutputShape(): array {
+ return [];
+ }
+
+ public function getOptionalOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function process(?string $userId, array $input, callable $reportProgress): array {
+ if (!isset($input['input']) || !$input['input'] instanceof File || !$input['input']->isReadable()) {
+ throw new RuntimeException('Invalid input file');
+ }
+ if ($this->appConfig->getAppValueBool('fail-' . $this->getId())) {
+ throw new ProcessingException('Failing as set by AppConfig');
+ }
+
+ $inputFile = $input['input'];
+ $transcription = 'Fake transcription result';
+
+ return ['output' => $transcription];
+ }
+}
diff --git a/apps/testing/lib/TaskProcessing/FakeTranslateProvider.php b/apps/testing/lib/TaskProcessing/FakeTranslateProvider.php
new file mode 100644
index 00000000000..22be8e83049
--- /dev/null
+++ b/apps/testing/lib/TaskProcessing/FakeTranslateProvider.php
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+declare(strict_types=1);
+
+namespace OCA\Testing\TaskProcessing;
+
+use OCA\Testing\AppInfo\Application;
+use OCP\AppFramework\Services\IAppConfig;
+use OCP\L10N\IFactory;
+use OCP\TaskProcessing\EShapeType;
+use OCP\TaskProcessing\Exception\ProcessingException;
+use OCP\TaskProcessing\ISynchronousProvider;
+use OCP\TaskProcessing\ShapeDescriptor;
+use OCP\TaskProcessing\ShapeEnumValue;
+use OCP\TaskProcessing\TaskTypes\TextToTextTranslate;
+use RuntimeException;
+
+class FakeTranslateProvider implements ISynchronousProvider {
+
+ public function __construct(
+ private IFactory $l10nFactory,
+ protected IAppConfig $appConfig,
+ ) {
+ }
+
+ public function getId(): string {
+ return Application::APP_ID . '-translate';
+ }
+
+ public function getName(): string {
+ return 'Fake translate task processing provider';
+ }
+
+ public function getTaskTypeId(): string {
+ return TextToTextTranslate::ID;
+ }
+
+ public function getExpectedRuntime(): int {
+ return 1;
+ }
+
+ public function getInputShapeEnumValues(): array {
+ $coreL = $this->l10nFactory->getLanguages();
+ $languages = array_merge($coreL['commonLanguages'], $coreL['otherLanguages']);
+ $languageEnumValues = array_map(static function (array $language) {
+ return new ShapeEnumValue($language['name'], $language['code']);
+ }, $languages);
+ $detectLanguageEnumValue = new ShapeEnumValue('Detect language', 'detect_language');
+ return [
+ 'origin_language' => array_merge([$detectLanguageEnumValue], $languageEnumValues),
+ 'target_language' => $languageEnumValues,
+ ];
+ }
+
+ public function getInputShapeDefaults(): array {
+ return [
+ 'origin_language' => 'detect_language',
+ ];
+ }
+
+ public function getOptionalInputShape(): array {
+ return [
+ 'max_tokens' => new ShapeDescriptor(
+ 'Maximum output words',
+ 'The maximum number of words/tokens that can be generated in the completion.',
+ EShapeType::Number
+ ),
+ 'model' => new ShapeDescriptor(
+ 'Model',
+ 'The model used to generate the completion',
+ EShapeType::Enum
+ ),
+ ];
+ }
+
+ public function getOptionalInputShapeEnumValues(): array {
+ return [
+ 'model' => [
+ new ShapeEnumValue('Model 1', 'model_1'),
+ new ShapeEnumValue('Model 2', 'model_2'),
+ new ShapeEnumValue('Model 3', 'model_3'),
+ ],
+ ];
+ }
+
+ public function getOptionalInputShapeDefaults(): array {
+ return [
+ 'max_tokens' => 200,
+ 'model' => 'model_3',
+ ];
+ }
+
+ public function getOptionalOutputShape(): array {
+ return [];
+ }
+
+ public function getOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ public function getOptionalOutputShapeEnumValues(): array {
+ return [];
+ }
+
+ private function getCoreLanguagesByCode(): array {
+ $coreL = $this->l10nFactory->getLanguages();
+ $coreLanguages = array_reduce(array_merge($coreL['commonLanguages'], $coreL['otherLanguages']), function ($carry, $val) {
+ $carry[$val['code']] = $val['name'];
+ return $carry;
+ });
+ return $coreLanguages;
+ }
+
+ public function process(?string $userId, array $input, callable $reportProgress): array {
+ if ($this->appConfig->getAppValueBool('fail-' . $this->getId())) {
+ throw new ProcessingException('Failing as set by AppConfig');
+ }
+
+ if (isset($input['model']) && is_string($input['model'])) {
+ $model = $input['model'];
+ } else {
+ $model = 'model_3';
+ }
+
+ if (!isset($input['input']) || !is_string($input['input'])) {
+ throw new RuntimeException('Invalid input text');
+ }
+ $inputText = $input['input'];
+
+ $maxTokens = null;
+ if (isset($input['max_tokens']) && is_int($input['max_tokens'])) {
+ $maxTokens = $input['max_tokens'];
+ }
+
+ $coreLanguages = $this->getCoreLanguagesByCode();
+
+ $toLanguage = $coreLanguages[$input['target_language']] ?? $input['target_language'];
+ if ($input['origin_language'] !== 'detect_language') {
+ $fromLanguage = $coreLanguages[$input['origin_language']] ?? $input['origin_language'];
+ $prompt = 'Fake translation from ' . $fromLanguage . ' to ' . $toLanguage . ': ' . $inputText;
+ } else {
+ $prompt = 'Fake Translation to ' . $toLanguage . ': ' . $inputText;
+ }
+
+ $fakeResult = $prompt . "\n\nModel: " . $model . "\nMax tokens: " . $maxTokens;
+
+ return ['output' => $fakeResult];
+ }
+}