From 31972cb26950a919f75d1cd44bb58def7789b864 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Fri, 6 Dec 2024 09:35:00 -0100 Subject: [PATCH] feat(config): implementation of lexicon Signed-off-by: Maxence Lange --- lib/composer/composer/autoload_classmap.php | 3 + lib/composer/composer/autoload_static.php | 3 + lib/private/AppConfig.php | 122 +++++++++++ .../Bootstrap/RegistrationContext.php | 34 ++++ lib/private/Config/UserConfig.php | 107 +++++++++- .../Bootstrap/IRegistrationContext.php | 11 + lib/public/IAppConfig.php | 3 + .../Config/Lexicon/ConfigLexiconEntry.php | 189 ++++++++++++++++++ .../Lexicon/ConfigLexiconStrictness.php | 30 +++ .../Config/Lexicon/IConfigLexicon.php | 44 ++++ lib/unstable/Config/ValueType.php | 25 +++ tests/lib/AppConfigTest.php | 5 + tests/lib/Config/LexiconTest.php | 151 ++++++++++++++ tests/lib/Config/TestConfigLexicon_E.php | 38 ++++ tests/lib/Config/TestConfigLexicon_I.php | 40 ++++ tests/lib/Config/TestConfigLexicon_N.php | 39 ++++ tests/lib/Config/TestConfigLexicon_W.php | 39 ++++ tests/lib/Config/UserConfigTest.php | 4 +- 18 files changed, 884 insertions(+), 3 deletions(-) create mode 100644 lib/unstable/Config/Lexicon/ConfigLexiconEntry.php create mode 100644 lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php create mode 100644 lib/unstable/Config/Lexicon/IConfigLexicon.php create mode 100644 tests/lib/Config/LexiconTest.php create mode 100644 tests/lib/Config/TestConfigLexicon_E.php create mode 100644 tests/lib/Config/TestConfigLexicon_I.php create mode 100644 tests/lib/Config/TestConfigLexicon_N.php create mode 100644 tests/lib/Config/TestConfigLexicon_W.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 746e43248d8f6..c68e57a06b49b 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -11,6 +11,9 @@ 'NCU\\Config\\Exceptions\\TypeConflictException' => $baseDir . '/lib/unstable/Config/Exceptions/TypeConflictException.php', 'NCU\\Config\\Exceptions\\UnknownKeyException' => $baseDir . '/lib/unstable/Config/Exceptions/UnknownKeyException.php', 'NCU\\Config\\IUserConfig' => $baseDir . '/lib/unstable/Config/IUserConfig.php', + 'NCU\\Config\\Lexicon\\ConfigLexiconEntry' => $baseDir . '/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php', + 'NCU\\Config\\Lexicon\\ConfigLexiconStrictness' => $baseDir . '/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php', + 'NCU\\Config\\Lexicon\\IConfigLexicon' => $baseDir . '/lib/unstable/Config/Lexicon/IConfigLexicon.php', 'NCU\\Config\\ValueType' => $baseDir . '/lib/unstable/Config/ValueType.php', 'NCU\\Federation\\ISignedCloudFederationProvider' => $baseDir . '/lib/unstable/Federation/ISignedCloudFederationProvider.php', 'NCU\\Security\\Signature\\Enum\\DigestAlgorithm' => $baseDir . '/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 62124d744968f..e292de7c883f2 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -52,6 +52,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'NCU\\Config\\Exceptions\\TypeConflictException' => __DIR__ . '/../../..' . '/lib/unstable/Config/Exceptions/TypeConflictException.php', 'NCU\\Config\\Exceptions\\UnknownKeyException' => __DIR__ . '/../../..' . '/lib/unstable/Config/Exceptions/UnknownKeyException.php', 'NCU\\Config\\IUserConfig' => __DIR__ . '/../../..' . '/lib/unstable/Config/IUserConfig.php', + 'NCU\\Config\\Lexicon\\ConfigLexiconEntry' => __DIR__ . '/../../..' . '/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php', + 'NCU\\Config\\Lexicon\\ConfigLexiconStrictness' => __DIR__ . '/../../..' . '/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php', + 'NCU\\Config\\Lexicon\\IConfigLexicon' => __DIR__ . '/../../..' . '/lib/unstable/Config/Lexicon/IConfigLexicon.php', 'NCU\\Config\\ValueType' => __DIR__ . '/../../..' . '/lib/unstable/Config/ValueType.php', 'NCU\\Federation\\ISignedCloudFederationProvider' => __DIR__ . '/../../..' . '/lib/unstable/Federation/ISignedCloudFederationProvider.php', 'NCU\\Security\\Signature\\Enum\\DigestAlgorithm' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php', diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index dc9bac7745d17..bb439218015dd 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -11,6 +11,10 @@ use InvalidArgumentException; use JsonException; +use NCU\Config\Lexicon\ConfigLexiconEntry; +use NCU\Config\Lexicon\ConfigLexiconStrictness; +use NCU\Config\Lexicon\IConfigLexicon; +use OC\AppFramework\Bootstrap\Coordinator; use OCP\DB\Exception as DBException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Exceptions\AppConfigIncorrectTypeException; @@ -55,6 +59,8 @@ class AppConfig implements IAppConfig { private array $valueTypes = []; // type for all config values private bool $fastLoaded = false; private bool $lazyLoaded = false; + /** @var array, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ + private array $configLexiconDetails = []; /** * $migrationCompleted is only needed to manage the previous structure @@ -430,6 +436,9 @@ private function getTypedValue( int $type, ): string { $this->assertParams($app, $key, valueType: $type); + if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type, $default)) { + return $default; // returns default if strictness of lexicon is set to WARNING (block and report) + } $this->loadConfig($app, $lazy); /** @@ -721,6 +730,9 @@ private function setTypedValue( int $type, ): bool { $this->assertParams($app, $key); + if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type)) { + return false; // returns false as database is not updated + } $this->loadConfig(null, $lazy); $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type); @@ -1559,4 +1571,114 @@ private function getSensitiveKeys(string $app): array { public function clearCachedConfig(): void { $this->clearCache(); } + + /** + * verify and compare current use of config values with defined lexicon + * + * @throws AppConfigUnknownKeyException + * @throws AppConfigTypeConflictException + * @return bool TRUE if everything is fine compared to lexicon or lexicon does not exist + */ + private function compareRegisteredConfigValues( + string $app, + string $key, + bool &$lazy, + int &$type, + string &$default = '', + ): bool { + if (in_array($key, + [ + 'enabled', + 'installed_version', + 'types', + ])) { + return true; // we don't break stuff for this list of config keys. + } + $configDetails = $this->getConfigDetailsFromLexicon($app); + if (!array_key_exists($key, $configDetails['entries'])) { + return $this->applyLexiconStrictness( + $configDetails['strictness'], + 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon' + ); + } + + /** @var ConfigLexiconEntry $configValue */ + $configValue = $configDetails['entries'][$key]; + $type &= ~self::VALUE_SENSITIVE; + + $appConfigValueType = $configValue->getValueType()->toAppConfigFlag(); + if ($type === self::VALUE_MIXED) { + $type = $appConfigValueType; // we overwrite if value was requested as mixed + } elseif ($appConfigValueType !== $type) { + throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon'); + } + + $lazy = $configValue->isLazy(); + $default = $configValue->getDefault() ?? $default; // default from Lexicon got priority + if ($configValue->isFlagged(self::FLAG_SENSITIVE)) { + $type |= self::VALUE_SENSITIVE; + } + if ($configValue->isDeprecated()) { + $this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.'); + } + + return true; + } + + /** + * manage ConfigLexicon behavior based on strictness set in IConfigLexicon + * + * @param ConfigLexiconStrictness|null $strictness + * @param string $line + * + * @return bool TRUE if conflict can be fully ignored, FALSE if action should be not performed + * @throws AppConfigUnknownKeyException if strictness implies exception + * @see IConfigLexicon::getStrictness() + */ + private function applyLexiconStrictness( + ?ConfigLexiconStrictness $strictness, + string $line = '', + ): bool { + if ($strictness === null) { + return true; + } + + switch ($strictness) { + case ConfigLexiconStrictness::IGNORE: + return true; + case ConfigLexiconStrictness::NOTICE: + $this->logger->notice($line); + return true; + case ConfigLexiconStrictness::WARNING: + $this->logger->warning($line); + return false; + } + + throw new AppConfigUnknownKeyException($line); + } + + /** + * extract details from registered $appId's config lexicon + * + * @param string $appId + * + * @return array{entries: array, strictness: ConfigLexiconStrictness} + */ + private function getConfigDetailsFromLexicon(string $appId): array { + if (!array_key_exists($appId, $this->configLexiconDetails)) { + $entries = []; + $bootstrapCoordinator = \OCP\Server::get(Coordinator::class); + $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId); + foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) { + $entries[$configEntry->getKey()] = $configEntry; + } + + $this->configLexiconDetails[$appId] = [ + 'entries' => $entries, + 'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE + ]; + } + + return $this->configLexiconDetails[$appId]; + } } diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index d7a380f9e1de8..f3b612edc38bb 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -10,6 +10,7 @@ namespace OC\AppFramework\Bootstrap; use Closure; +use NCU\Config\Lexicon\IConfigLexicon; use OC\Support\CrashReport\Registry; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IRegistrationContext; @@ -141,6 +142,9 @@ class RegistrationContext { /** @var ServiceRegistration[] */ private array $declarativeSettings = []; + /** @var array */ + private array $configLexiconClasses = []; + /** @var ServiceRegistration[] */ private array $teamResourceProviders = []; @@ -422,6 +426,13 @@ public function registerMailProvider(string $class): void { $class ); } + + public function registerConfigLexicon(string $configLexiconClass): void { + $this->context->registerConfigLexicon( + $this->appId, + $configLexiconClass + ); + } }; } @@ -621,6 +632,13 @@ public function registerMailProvider(string $appId, string $class): void { $this->mailProviders[] = new ServiceRegistration($appId, $class); } + /** + * @psalm-param class-string $configLexiconClass + */ + public function registerConfigLexicon(string $appId, string $configLexiconClass): void { + $this->configLexiconClasses[$appId] = $configLexiconClass; + } + /** * @param App[] $apps */ @@ -972,4 +990,20 @@ public function getTaskProcessingTaskTypes(): array { public function getMailProviders(): array { return $this->mailProviders; } + + /** + * returns IConfigLexicon registered by the app. + * null if none registered. + * + * @param string $appId + * + * @return IConfigLexicon|null + */ + public function getConfigLexicon(string $appId): ?IConfigLexicon { + if (!array_key_exists($appId, $this->configLexiconClasses)) { + return null; + } + + return \OCP\Server::get($this->configLexiconClasses[$appId]); + } } diff --git a/lib/private/Config/UserConfig.php b/lib/private/Config/UserConfig.php index 37e109b2121a5..b2242729d2bfe 100644 --- a/lib/private/Config/UserConfig.php +++ b/lib/private/Config/UserConfig.php @@ -15,7 +15,10 @@ use NCU\Config\Exceptions\TypeConflictException; use NCU\Config\Exceptions\UnknownKeyException; use NCU\Config\IUserConfig; +use NCU\Config\Lexicon\ConfigLexiconEntry; +use NCU\Config\Lexicon\ConfigLexiconStrictness; use NCU\Config\ValueType; +use OC\AppFramework\Bootstrap\Coordinator; use OCP\DB\Exception as DBException; use OCP\DB\IResult; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -63,6 +66,8 @@ class UserConfig implements IUserConfig { private array $fastLoaded = []; /** @var array ['user_id' => bool] */ private array $lazyLoaded = []; + /** @var array, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ + private array $configLexiconDetails = []; public function __construct( protected IDBConnection $connection, @@ -706,6 +711,9 @@ private function getTypedValue( ValueType $type, ): string { $this->assertParams($userId, $app, $key); + if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type, default: $default)) { + return $default; // returns default if strictness of lexicon is set to WARNING (block and report) + } $this->loadConfig($userId, $lazy); /** @@ -1038,6 +1046,9 @@ private function setTypedValue( ValueType $type, ): bool { $this->assertParams($userId, $app, $key); + if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type, $flags)) { + return false; // returns false as database is not updated + } $this->loadConfig($userId, $lazy); $inserted = $refreshCache = false; @@ -1045,7 +1056,7 @@ private function setTypedValue( $sensitive = $this->isFlagged(self::FLAG_SENSITIVE, $flags); if ($sensitive || ($this->hasKey($userId, $app, $key, $lazy) && $this->isSensitive($userId, $app, $key, $lazy))) { $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value); - $flags |= UserConfig::FLAG_SENSITIVE; + $flags |= self::FLAG_SENSITIVE; } // if requested, we fill the 'indexed' field with current value @@ -1803,4 +1814,98 @@ private function decryptSensitiveValue(string $userId, string $app, string $key, ]); } } + + /** + * verify and compare current use of config values with defined lexicon + * + * @throws UnknownKeyException + * @throws TypeConflictException + */ + private function compareRegisteredConfigValues( + string $app, + string $key, + bool &$lazy, + ValueType &$type, + int &$flags = 0, + string &$default = '', + ): bool { + $configDetails = $this->getConfigDetailsFromLexicon($app); + if (!array_key_exists($key, $configDetails['entries'])) { + return $this->applyLexiconStrictness($configDetails['strictness'], 'The user config key ' . $app . '/' . $key . ' is not defined in the config lexicon'); + } + + /** @var ConfigLexiconEntry $configValue */ + $configValue = $configDetails['entries'][$key]; + if ($type === ValueType::MIXED) { + $type = $configValue->getValueType()->value; // we overwrite if value was requested as mixed + } elseif ($configValue->getValueType() !== $type) { + throw new TypeConflictException('The user config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon'); + } + + $lazy = $configValue->isLazy(); + $default = $configValue->getDefault() ?? $default; // default from Lexicon got priority + $flags = $configValue->getFlags(); + + if ($configValue->isDeprecated()) { + $this->logger->notice('User config key ' . $app . '/' . $key . ' is set as deprecated.'); + } + + return true; + } + + /** + * manage ConfigLexicon behavior based on strictness set in IConfigLexicon + * + * @see IConfigLexicon::getStrictness() + * @param ConfigLexiconStrictness|null $strictness + * @param string $line + * + * @return bool TRUE if conflict can be fully ignored + * @throws UnknownKeyException + */ + private function applyLexiconStrictness(?ConfigLexiconStrictness $strictness, string $line = ''): bool { + if ($strictness === null) { + return true; + } + + switch ($strictness) { + case ConfigLexiconStrictness::IGNORE: + return true; + case ConfigLexiconStrictness::NOTICE: + $this->logger->notice($line); + return true; + case ConfigLexiconStrictness::WARNING: + $this->logger->warning($line); + return false; + case ConfigLexiconStrictness::EXCEPTION: + throw new UnknownKeyException($line); + } + + throw new UnknownKeyException($line); + } + + /** + * extract details from registered $appId's config lexicon + * + * @param string $appId + * + * @return array{entries: array, strictness: ConfigLexiconStrictness} + */ + private function getConfigDetailsFromLexicon(string $appId): array { + if (!array_key_exists($appId, $this->configLexiconDetails)) { + $entries = []; + $bootstrapCoordinator = \OCP\Server::get(Coordinator::class); + $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId); + foreach ($configLexicon?->getUserConfigs() ?? [] as $configEntry) { + $entries[$configEntry->getKey()] = $configEntry; + } + + $this->configLexiconDetails[$appId] = [ + 'entries' => $entries, + 'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE + ]; + } + + return $this->configLexiconDetails[$appId]; + } } diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php index b9e5413e5c260..8a18ec8ae9da0 100644 --- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php +++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php @@ -423,4 +423,15 @@ public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeCla */ public function registerMailProvider(string $class): void; + + /** + * Register an implementation of \OCP\Config\Lexicon\IConfigLexicon that + * will handle the config lexicon + * + * @param string $configLexiconClass + * + * @psalm-param class-string<\NCU\Config\Lexicon\IConfigLexicon> $configLexiconClass + * @since 31.0.0 + */ + public function registerConfigLexicon(string $configLexiconClass): void; } diff --git a/lib/public/IAppConfig.php b/lib/public/IAppConfig.php index fe894da8d3156..d4d5c1c09c740 100644 --- a/lib/public/IAppConfig.php +++ b/lib/public/IAppConfig.php @@ -45,6 +45,9 @@ interface IAppConfig { /** @since 29.0.0 */ public const VALUE_ARRAY = 64; + /** @since 31.0.0 */ + public const FLAG_SENSITIVE = 1; // value is sensitive + /** * Get list of all apps that have at least one config value stored in database * diff --git a/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php b/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php new file mode 100644 index 0000000000000..68787f9000c6b --- /dev/null +++ b/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php @@ -0,0 +1,189 @@ +default = match ($type) { + ValueType::MIXED => (string)$default, + ValueType::STRING => $this->convertFromString((string)$default), + ValueType::INT => $this->convertFromInt((int)$default), + ValueType::FLOAT => $this->convertFromFloat((float)$default), + ValueType::BOOL => $this->convertFromBool((bool)$default), + ValueType::ARRAY => $this->convertFromArray((array)$default) + }; + } + + /** @psalm-suppress UndefinedClass */ + if (\OC::$CLI) { // only store definition if ran from CLI + $this->definition = $definition; + } + } + + /** + * @inheritDoc + * + * @return string config key + * @experimental 31.0.0 + */ + public function getKey(): string { + return $this->key; + } + + /** + * @inheritDoc + * + * @return ValueType + * @experimental 31.0.0 + */ + public function getValueType(): ValueType { + return $this->type; + } + + /** + * @param string $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromString(string $default): string { + return $default; + } + + /** + * @param int $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromInt(int $default): string { + return (string)$default; + } + + /** + * @param float $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromFloat(float $default): string { + return (string)$default; + } + + /** + * @param bool $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromBool(bool $default): string { + return ($default) ? '1' : '0'; + } + + /** + * @param array $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromArray(array $default): string { + return json_encode($default); + } + + /** + * @inheritDoc + * + * @return string|null NULL if no default is set + * @experimental 31.0.0 + */ + public function getDefault(): ?string { + return $this->default; + } + + /** + * @inheritDoc + * + * @return string + * @experimental 31.0.0 + */ + public function getDefinition(): string { + return $this->definition; + } + + /** + * @inheritDoc + * + * @see IAppConfig for details on lazy config values + * @return bool TRUE if config value is lazy + * @experimental 31.0.0 + */ + public function isLazy(): bool { + return $this->lazy; + } + + /** + * @inheritDoc + * + * @see IAppConfig for details on sensitive config values + * @return int bitflag about the config value + * @experimental 31.0.0 + */ + public function getFlags(): int { + return $this->flags; + } + + /** + * @param int $flag + * + * @return bool TRUE is config value bitflag contains $flag + * @experimental 31.0.0 + */ + public function isFlagged(int $flag): bool { + return (bool)($flag & $this->getFlags()); + } + + /** + * @inheritDoc + * + * @return bool TRUE if config si deprecated + * @experimental 31.0.0 + */ + public function isDeprecated(): bool { + return $this->deprecated; + } +} diff --git a/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php b/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php new file mode 100644 index 0000000000000..fda0adb003720 --- /dev/null +++ b/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php @@ -0,0 +1,30 @@ +value); } } + + /** + * get corresponding AppConfig flag value + * + * @return int + * @throws IncorrectTypeException + * + * @experimental 31.0.0 + */ + public function toAppConfigFlag(): int { + try { + return match ($this) { + self::MIXED => IAppConfig::VALUE_MIXED, + self::STRING => IAppConfig::VALUE_STRING, + self::INT => IAppConfig::VALUE_INT, + self::FLOAT => IAppConfig::VALUE_FLOAT, + self::BOOL => IAppConfig::VALUE_BOOL, + self::ARRAY => IAppConfig::VALUE_ARRAY, + }; + } catch (UnhandledMatchError) { + throw new IncorrectTypeException('unknown type definition ' . $this->value); + } + } + } diff --git a/tests/lib/AppConfigTest.php b/tests/lib/AppConfigTest.php index e8c10fe654b17..775c9027dd671 100644 --- a/tests/lib/AppConfigTest.php +++ b/tests/lib/AppConfigTest.php @@ -9,6 +9,7 @@ use InvalidArgumentException; use OC\AppConfig; +use OC\AppFramework\Bootstrap\Coordinator; use OCP\Exceptions\AppConfigTypeConflictException; use OCP\Exceptions\AppConfigUnknownKeyException; use OCP\IAppConfig; @@ -28,6 +29,8 @@ class AppConfigTest extends TestCase { protected IDBConnection $connection; private LoggerInterface $logger; private ICrypto $crypto; + private Coordinator $coordinator; + private array $originalConfig; /** @@ -88,6 +91,7 @@ protected function setUp(): void { $this->connection = \OCP\Server::get(IDBConnection::class); $this->logger = \OCP\Server::get(LoggerInterface::class); $this->crypto = \OCP\Server::get(ICrypto::class); + $this->coordinator = \OCP\Server::get(Coordinator::class); // storing current config and emptying the data table $sql = $this->connection->getQueryBuilder(); @@ -178,6 +182,7 @@ private function generateAppConfig(bool $preLoading = true): IAppConfig { $this->connection, $this->logger, $this->crypto, + $this->coordinator ); $msg = ' generateAppConfig() failed to confirm cache status'; diff --git a/tests/lib/Config/LexiconTest.php b/tests/lib/Config/LexiconTest.php new file mode 100644 index 0000000000000..5bcd3509b2241 --- /dev/null +++ b/tests/lib/Config/LexiconTest.php @@ -0,0 +1,151 @@ +getRegistrationContext()?->registerConfigLexicon(TestConfigLexicon_I::APPID, TestConfigLexicon_I::class); + $bootstrapCoordinator->getRegistrationContext()?->registerConfigLexicon(TestConfigLexicon_N::APPID, TestConfigLexicon_N::class); + $bootstrapCoordinator->getRegistrationContext()?->registerConfigLexicon(TestConfigLexicon_W::APPID, TestConfigLexicon_W::class); + $bootstrapCoordinator->getRegistrationContext()?->registerConfigLexicon(TestConfigLexicon_E::APPID, TestConfigLexicon_E::class); + + $this->appConfig = Server::get(IAppConfig::class); + $this->userConfig = Server::get(IUserConfig::class); + } + + protected function tearDown(): void { + parent::tearDown(); + + $this->appConfig->deleteApp(TestConfigLexicon_I::APPID); + $this->appConfig->deleteApp(TestConfigLexicon_N::APPID); + $this->appConfig->deleteApp(TestConfigLexicon_W::APPID); + $this->appConfig->deleteApp(TestConfigLexicon_E::APPID); + + $this->userConfig->deleteApp(TestConfigLexicon_I::APPID); + $this->userConfig->deleteApp(TestConfigLexicon_N::APPID); + $this->userConfig->deleteApp(TestConfigLexicon_W::APPID); + $this->userConfig->deleteApp(TestConfigLexicon_E::APPID); + } + + public function testAppLexiconSetCorrect() { + $this->assertSame(true, $this->appConfig->setValueString(TestConfigLexicon_E::APPID, 'key1', 'new_value')); + $this->assertSame(true, $this->appConfig->isLazy(TestConfigLexicon_E::APPID, 'key1')); + $this->assertSame(true, $this->appConfig->isSensitive(TestConfigLexicon_E::APPID, 'key1')); + $this->appConfig->deleteKey(TestConfigLexicon_E::APPID, 'key1'); + } + + public function testAppLexiconGetCorrect() { + $this->assertSame('abcde', $this->appConfig->getValueString(TestConfigLexicon_E::APPID, 'key1', 'default')); + } + + public function testAppLexiconSetIncorrectValueType() { + $this->expectException(AppConfigTypeConflictException::class); + $this->appConfig->setValueInt(TestConfigLexicon_E::APPID, 'key1', -1); + } + + public function testAppLexiconGetIncorrectValueType() { + $this->expectException(AppConfigTypeConflictException::class); + $this->appConfig->getValueInt(TestConfigLexicon_E::APPID, 'key1'); + } + + public function testAppLexiconIgnore() { + $this->appConfig->setValueString(TestConfigLexicon_I::APPID, 'key_ignore', 'new_value'); + $this->assertSame('new_value', $this->appConfig->getValueString(TestConfigLexicon_I::APPID, 'key_ignore', '')); + } + + public function testAppLexiconNotice() { + $this->appConfig->setValueString(TestConfigLexicon_N::APPID, 'key_notice', 'new_value'); + $this->assertSame('new_value', $this->appConfig->getValueString(TestConfigLexicon_N::APPID, 'key_notice', '')); + } + + public function testAppLexiconWarning() { + $this->appConfig->setValueString(TestConfigLexicon_W::APPID, 'key_warning', 'new_value'); + $this->assertSame('', $this->appConfig->getValueString(TestConfigLexicon_W::APPID, 'key_warning', '')); + } + + public function testAppLexiconSetException() { + $this->expectException(AppConfigUnknownKeyException::class); + $this->appConfig->setValueString(TestConfigLexicon_E::APPID, 'key_exception', 'new_value'); + $this->assertSame('', $this->appConfig->getValueString(TestConfigLexicon_E::APPID, 'key3', '')); + } + + public function testAppLexiconGetException() { + $this->expectException(AppConfigUnknownKeyException::class); + $this->appConfig->getValueString(TestConfigLexicon_E::APPID, 'key_exception'); + } + + public function testUserLexiconSetCorrect() { + $this->assertSame(true, $this->userConfig->setValueString('user1', TestConfigLexicon_E::APPID, 'key1', 'new_value')); + $this->assertSame(true, $this->userConfig->isLazy('user1', TestConfigLexicon_E::APPID, 'key1')); + $this->assertSame(true, $this->userConfig->isSensitive('user1', TestConfigLexicon_E::APPID, 'key1')); + $this->userConfig->deleteKey(TestConfigLexicon_E::APPID, 'key1'); + } + + public function testUserLexiconGetCorrect() { + $this->assertSame('abcde', $this->userConfig->getValueString('user1', TestConfigLexicon_E::APPID, 'key1', 'default')); + } + + public function testUserLexiconSetIncorrectValueType() { + $this->expectException(TypeConflictException::class); + $this->userConfig->setValueInt('user1', TestConfigLexicon_E::APPID, 'key1', -1); + } + + public function testUserLexiconGetIncorrectValueType() { + $this->expectException(TypeConflictException::class); + $this->userConfig->getValueInt('user1', TestConfigLexicon_E::APPID, 'key1'); + } + + public function testUserLexiconIgnore() { + $this->userConfig->setValueString('user1', TestConfigLexicon_I::APPID, 'key_ignore', 'new_value'); + $this->assertSame('new_value', $this->userConfig->getValueString('user1', TestConfigLexicon_I::APPID, 'key_ignore', '')); + } + + public function testUserLexiconNotice() { + $this->userConfig->setValueString('user1', TestConfigLexicon_N::APPID, 'key_notice', 'new_value'); + $this->assertSame('new_value', $this->userConfig->getValueString('user1', TestConfigLexicon_N::APPID, 'key_notice', '')); + } + + public function testUserLexiconWarning() { + $this->userConfig->setValueString('user1', TestConfigLexicon_W::APPID, 'key_warning', 'new_value'); + $this->assertSame('', $this->userConfig->getValueString('user1', TestConfigLexicon_W::APPID, 'key_warning', '')); + } + + public function testUserLexiconSetException() { + $this->expectException(UnknownKeyException::class); + $this->userConfig->setValueString('user1', TestConfigLexicon_E::APPID, 'key_exception', 'new_value'); + $this->assertSame('', $this->userConfig->getValueString('user1', TestConfigLexicon_E::APPID, 'key3', '')); + } + + public function testUserLexiconGetException() { + $this->expectException(UnknownKeyException::class); + $this->userConfig->getValueString('user1', TestConfigLexicon_E::APPID, 'key_exception'); + } +} diff --git a/tests/lib/Config/TestConfigLexicon_E.php b/tests/lib/Config/TestConfigLexicon_E.php new file mode 100644 index 0000000000000..e0890cbd76e14 --- /dev/null +++ b/tests/lib/Config/TestConfigLexicon_E.php @@ -0,0 +1,38 @@ +generateUserConfig(); $this->assertEqualsCanonicalizing(