diff options
Diffstat (limited to 'apps/testing/lib')
23 files changed, 1797 insertions, 0 deletions
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]; + } +} |