* * @author Maxence Lange * * @license AGPL-3.0 or later * * 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 * */ namespace Test; use InvalidArgumentException; use OC\AppConfig; use OCP\Exceptions\AppConfigTypeConflictException; use OCP\Exceptions\AppConfigUnknownKeyException; use OCP\IAppConfig; use OCP\IDBConnection; use OCP\Security\ICrypto; use Psr\Log\LoggerInterface; /** * Class AppConfigTest * * @group DB * * @package Test */ class AppConfigTest extends TestCase { protected IAppConfig $appConfig; protected IDBConnection $connection; private LoggerInterface $logger; private ICrypto $crypto; private array $originalConfig; /** * @var array>> * [appId => [configKey, configValue, valueType, lazy, sensitive]] */ private array $baseStruct = [ 'testapp' => [ 'enabled' => ['enabled', 'true'], 'installed_version' => ['installed_version', '1.2.3'], 'depends_on' => ['depends_on', 'someapp'], 'deletethis' => ['deletethis', 'deletethis'], 'key' => ['key', 'value'] ], 'someapp' => [ 'key' => ['key', 'value'], 'otherkey' => ['otherkey', 'othervalue'] ], '123456' => [ 'enabled' => ['enabled', 'true'], 'key' => ['key', 'value'] ], 'anotherapp' => [ 'enabled' => ['enabled', 'false'], 'key' => ['key', 'value'] ], 'non-sensitive-app' => [ 'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, false], 'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, false], ], 'sensitive-app' => [ 'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, true], 'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, true], ], 'only-lazy' => [ 'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true] ], 'typed' => [ 'mixed' => ['mixed', 'mix', IAppConfig::VALUE_MIXED], 'string' => ['string', 'value', IAppConfig::VALUE_STRING], 'int' => ['int', '42', IAppConfig::VALUE_INT], 'float' => ['float', '3.14', IAppConfig::VALUE_FLOAT], 'bool' => ['bool', '1', IAppConfig::VALUE_BOOL], 'array' => ['array', '{"test": 1}', IAppConfig::VALUE_ARRAY], ], 'prefix-app' => [ 'key1' => ['key1', 'value'], 'prefix1' => ['prefix1', 'value'], 'prefix-2' => ['prefix-2', 'value'], 'key-2' => ['key-2', 'value'], ] ]; protected function setUp(): void { parent::setUp(); $this->connection = \OCP\Server::get(IDBConnection::class); $this->logger = \OCP\Server::get(LoggerInterface::class); $this->crypto = \OCP\Server::get(ICrypto::class); // storing current config and emptying the data table $sql = $this->connection->getQueryBuilder(); $sql->select('*') ->from('appconfig'); $result = $sql->executeQuery(); $this->originalConfig = $result->fetchAll(); $result->closeCursor(); $sql = $this->connection->getQueryBuilder(); $sql->delete('appconfig'); $sql->executeStatement(); $sql = $this->connection->getQueryBuilder(); $sql->insert('appconfig') ->values( [ 'appid' => $sql->createParameter('appid'), 'configkey' => $sql->createParameter('configkey'), 'configvalue' => $sql->createParameter('configvalue'), 'type' => $sql->createParameter('type'), 'lazy' => $sql->createParameter('lazy') ] ); foreach ($this->baseStruct as $appId => $appData) { foreach ($appData as $key => $row) { $value = $row[1]; $type = $row[2] ?? IAppConfig::VALUE_MIXED; if (($row[4] ?? false) === true) { $type |= IAppConfig::VALUE_SENSITIVE; $value = self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX') . $this->crypto->encrypt($value); $this->baseStruct[$appId][$key]['encrypted'] = $value; } $sql->setParameters( [ 'appid' => $appId, 'configkey' => $row[0], 'configvalue' => $value, 'type' => $type, 'lazy' => (($row[3] ?? false) === true) ? 1 : 0 ] )->executeStatement(); } } } protected function tearDown(): void { $sql = $this->connection->getQueryBuilder(); $sql->delete('appconfig'); $sql->executeStatement(); $sql = $this->connection->getQueryBuilder(); $sql->insert('appconfig') ->values( [ 'appid' => $sql->createParameter('appid'), 'configkey' => $sql->createParameter('configkey'), 'configvalue' => $sql->createParameter('configvalue'), 'lazy' => $sql->createParameter('lazy'), 'type' => $sql->createParameter('type'), ] ); foreach ($this->originalConfig as $key => $configs) { $sql->setParameter('appid', $configs['appid']) ->setParameter('configkey', $configs['configkey']) ->setParameter('configvalue', $configs['configvalue']) ->setParameter('lazy', ($configs['lazy'] === '1') ? '1' : '0') ->setParameter('type', $configs['type']); $sql->executeStatement(); } // $this->restoreService(AppConfig::class); parent::tearDown(); } /** * @param bool $preLoading TRUE will preload the 'fast' cache, which is the normal behavior of usual * IAppConfig * * @return IAppConfig */ private function generateAppConfig(bool $preLoading = true): IAppConfig { /** @var AppConfig $config */ $config = new \OC\AppConfig( $this->connection, $this->logger, $this->crypto, ); $msg = ' generateAppConfig() failed to confirm cache status'; // confirm cache status $status = $config->statusCache(); $this->assertSame(false, $status['fastLoaded'], $msg); $this->assertSame(false, $status['lazyLoaded'], $msg); $this->assertSame([], $status['fastCache'], $msg); $this->assertSame([], $status['lazyCache'], $msg); if ($preLoading) { // simple way to initiate the load of non-lazy config values in cache $config->getValueString('core', 'preload', ''); // confirm cache status $status = $config->statusCache(); $this->assertSame(true, $status['fastLoaded'], $msg); $this->assertSame(false, $status['lazyLoaded'], $msg); $apps = array_values(array_diff(array_keys($this->baseStruct), ['only-lazy'])); $this->assertEqualsCanonicalizing($apps, array_keys($status['fastCache']), $msg); $this->assertSame([], array_keys($status['lazyCache']), $msg); } return $config; } public function testGetApps(): void { $config = $this->generateAppConfig(false); $this->assertEqualsCanonicalizing(array_keys($this->baseStruct), $config->getApps()); } /** * returns list of app and their keys * * @return array ['appId' => ['key1', 'key2', ]] * @see testGetKeys */ public function providerGetAppKeys(): array { $appKeys = []; foreach ($this->baseStruct as $appId => $appData) { $keys = []; foreach ($appData as $row) { $keys[] = $row[0]; } $appKeys[] = [(string)$appId, $keys]; } return $appKeys; } /** * returns list of config keys * * @return array [appId, key, value, type, lazy, sensitive] * @see testIsSensitive * @see testIsLazy * @see testGetKeys */ public function providerGetKeys(): array { $appKeys = []; foreach ($this->baseStruct as $appId => $appData) { foreach ($appData as $row) { $appKeys[] = [ (string)$appId, $row[0], $row[1], $row[2] ?? IAppConfig::VALUE_MIXED, $row[3] ?? false, $row[4] ?? false ]; } } return $appKeys; } /** * @dataProvider providerGetAppKeys * * @param string $appId * @param array $expectedKeys */ public function testGetKeys(string $appId, array $expectedKeys): void { $config = $this->generateAppConfig(); $this->assertEqualsCanonicalizing($expectedKeys, $config->getKeys($appId)); } public function testGetKeysOnUnknownAppShouldReturnsEmptyArray(): void { $config = $this->generateAppConfig(); $this->assertEqualsCanonicalizing([], $config->getKeys('unknown-app')); } /** * @dataProvider providerGetKeys * * @param string $appId * @param string $configKey * @param string $value * @param bool $lazy */ public function testHasKey(string $appId, string $configKey, string $value, int $type, bool $lazy): void { $config = $this->generateAppConfig(); $this->assertEquals(true, $config->hasKey($appId, $configKey, $lazy)); } public function testHasKeyOnNonExistentKeyReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertEquals(false, $config->hasKey(array_keys($this->baseStruct)[0], 'inexistant-key')); } public function testHasKeyOnUnknownAppReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertEquals(false, $config->hasKey('inexistant-app', 'inexistant-key')); } public function testHasKeyOnMistypedAsLazyReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->hasKey('non-sensitive-app', 'non-lazy-key', true)); } public function testHasKeyOnMistypeAsNonLazyReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->hasKey('non-sensitive-app', 'lazy-key', false)); } public function testHasKeyOnMistypeAsNonLazyReturnsTrueWithLazyArgumentIsNull(): void { $config = $this->generateAppConfig(); $this->assertSame(true, $config->hasKey('non-sensitive-app', 'lazy-key', null)); } /** * @dataProvider providerGetKeys */ public function testIsSensitive( string $appId, string $configKey, string $configValue, int $type, bool $lazy, bool $sensitive ): void { $config = $this->generateAppConfig(); $this->assertEquals($sensitive, $config->isSensitive($appId, $configKey, $lazy)); } public function testIsSensitiveOnNonExistentKeyThrowsException(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigUnknownKeyException::class); $config->isSensitive(array_keys($this->baseStruct)[0], 'inexistant-key'); } public function testIsSensitiveOnUnknownAppThrowsException(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigUnknownKeyException::class); $config->isSensitive('unknown-app', 'inexistant-key'); } public function testIsSensitiveOnSensitiveMistypedAsLazy(): void { $config = $this->generateAppConfig(); $this->assertSame(true, $config->isSensitive('sensitive-app', 'non-lazy-key', true)); } public function testIsSensitiveOnNonSensitiveMistypedAsLazy(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->isSensitive('non-sensitive-app', 'non-lazy-key', true)); } public function testIsSensitiveOnSensitiveMistypedAsNonLazyThrowsException(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigUnknownKeyException::class); $config->isSensitive('sensitive-app', 'lazy-key', false); } public function testIsSensitiveOnNonSensitiveMistypedAsNonLazyThrowsException(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigUnknownKeyException::class); $config->isSensitive('non-sensitive-app', 'lazy-key', false); } /** * @dataProvider providerGetKeys */ public function testIsLazy(string $appId, string $configKey, string $configValue, int $type, bool $lazy ): void { $config = $this->generateAppConfig(); $this->assertEquals($lazy, $config->isLazy($appId, $configKey)); } public function testIsLazyOnNonExistentKeyThrowsException(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigUnknownKeyException::class); $config->isLazy(array_keys($this->baseStruct)[0], 'inexistant-key'); } public function testIsLazyOnUnknownAppThrowsException(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigUnknownKeyException::class); $config->isLazy('unknown-app', 'inexistant-key'); } public function testGetAllValues(): void { $config = $this->generateAppConfig(); $this->assertEquals( [ 'array' => ['test' => 1], 'bool' => true, 'float' => 3.14, 'int' => 42, 'mixed' => 'mix', 'string' => 'value', ], $config->getAllValues('typed') ); } public function testGetAllValuesWithEmptyApp(): void { $config = $this->generateAppConfig(); $this->expectException(InvalidArgumentException::class); $config->getAllValues(''); } /** * @dataProvider providerGetAppKeys * * @param string $appId * @param array $keys */ public function testGetAllValuesWithEmptyKey(string $appId, array $keys): void { $config = $this->generateAppConfig(); $this->assertEqualsCanonicalizing($keys, array_keys($config->getAllValues($appId, ''))); } public function testGetAllValuesWithPrefix(): void { $config = $this->generateAppConfig(); $this->assertEqualsCanonicalizing(['prefix1', 'prefix-2'], array_keys($config->getAllValues('prefix-app', 'prefix'))); } public function testSearchValues(): void { $config = $this->generateAppConfig(); $this->assertEqualsCanonicalizing(['testapp' => 'true', '123456' => 'true', 'anotherapp' => 'false'], $config->searchValues('enabled')); } public function testGetValueString(): void { $config = $this->generateAppConfig(); $this->assertSame('value', $config->getValueString('typed', 'string', '')); } public function testGetValueStringOnUnknownAppReturnsDefault(): void { $config = $this->generateAppConfig(); $this->assertSame('default-1', $config->getValueString('typed-1', 'string', 'default-1')); } public function testGetValueStringOnNonExistentKeyReturnsDefault(): void { $config = $this->generateAppConfig(); $this->assertSame('default-2', $config->getValueString('typed', 'string-2', 'default-2')); } public function testGetValueStringOnWrongType(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigTypeConflictException::class); $config->getValueString('typed', 'int'); } public function testGetNonLazyValueStringAsLazy(): void { $config = $this->generateAppConfig(); $this->assertSame('value', $config->getValueString('non-sensitive-app', 'non-lazy-key', 'default', lazy: true)); } public function testGetValueInt() { $config = $this->generateAppConfig(); $this->assertSame(42, $config->getValueInt('typed', 'int', 0)); } public function testGetValueIntOnUnknownAppReturnsDefault(): void { $config = $this->generateAppConfig(); $this->assertSame(1, $config->getValueInt('typed-1', 'int', 1)); } public function testGetValueIntOnNonExistentKeyReturnsDefault() { $config = $this->generateAppConfig(); $this->assertSame(2, $config->getValueInt('typed', 'int-2', 2)); } public function testGetValueIntOnWrongType(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigTypeConflictException::class); $config->getValueInt('typed', 'float'); } public function testGetValueFloat() { $config = $this->generateAppConfig(); $this->assertSame(3.14, $config->getValueFloat('typed', 'float', 0)); } public function testGetValueFloatOnNonUnknownAppReturnsDefault(): void { $config = $this->generateAppConfig(); $this->assertSame(1.11, $config->getValueFloat('typed-1', 'float', 1.11)); } public function testGetValueFloatOnNonExistentKeyReturnsDefault() { $config = $this->generateAppConfig(); $this->assertSame(2.22, $config->getValueFloat('typed', 'float-2', 2.22)); } public function testGetValueFloatOnWrongType(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigTypeConflictException::class); $config->getValueFloat('typed', 'bool'); } public function testGetValueBool(): void { $config = $this->generateAppConfig(); $this->assertSame(true, $config->getValueBool('typed', 'bool')); } public function testGetValueBoolOnUnknownAppReturnsDefault(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->getValueBool('typed-1', 'bool', false)); } public function testGetValueBoolOnNonExistentKeyReturnsDefault(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->getValueBool('typed', 'bool-2')); } public function testGetValueBoolOnWrongType(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigTypeConflictException::class); $config->getValueBool('typed', 'array'); } public function testGetValueArray(): void { $config = $this->generateAppConfig(); $this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('typed', 'array', [])); } public function testGetValueArrayOnUnknownAppReturnsDefault(): void { $config = $this->generateAppConfig(); $this->assertSame([1], $config->getValueArray('typed-1', 'array', [1])); } public function testGetValueArrayOnNonExistentKeyReturnsDefault(): void { $config = $this->generateAppConfig(); $this->assertSame([1, 2], $config->getValueArray('typed', 'array-2', [1, 2])); } public function testGetValueArrayOnWrongType(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigTypeConflictException::class); $config->getValueArray('typed', 'string'); } /** * @return array * @see testGetValueType * * @see testGetValueMixed */ public function providerGetValueMixed(): array { return [ // key, value, type ['mixed', 'mix', IAppConfig::VALUE_MIXED], ['string', 'value', IAppConfig::VALUE_STRING], ['int', '42', IAppConfig::VALUE_INT], ['float', '3.14', IAppConfig::VALUE_FLOAT], ['bool', '1', IAppConfig::VALUE_BOOL], ['array', '{"test": 1}', IAppConfig::VALUE_ARRAY], ]; } /** * @dataProvider providerGetValueMixed * * @param string $key * @param string $value */ public function testGetValueMixed(string $key, string $value): void { $config = $this->generateAppConfig(); $this->assertSame($value, $config->getValueMixed('typed', $key)); } /** * @dataProvider providerGetValueMixed * * @param string $key * @param string $value * @param int $type */ public function testGetValueType(string $key, string $value, int $type): void { $config = $this->generateAppConfig(); $this->assertSame($type, $config->getValueType('typed', $key)); } public function testGetValueTypeOnUnknownApp(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigUnknownKeyException::class); $config->getValueType('typed-1', 'string'); } public function testGetValueTypeOnNonExistentKey(): void { $config = $this->generateAppConfig(); $this->expectException(AppConfigUnknownKeyException::class); $config->getValueType('typed', 'string-2'); } public function testSetValueString(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1'); $this->assertSame('value-1', $config->getValueString('feed', 'string', '')); } public function testSetValueStringCache(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1'); $status = $config->statusCache(); $this->assertSame('value-1', $status['fastCache']['feed']['string']); } public function testSetValueStringDatabase(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1'); $config->clearCache(); $this->assertSame('value-1', $config->getValueString('feed', 'string', '')); } public function testSetValueStringIsUpdated(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1'); $this->assertSame(true, $config->setValueString('feed', 'string', 'value-2')); } public function testSetValueStringIsNotUpdated(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1'); $this->assertSame(false, $config->setValueString('feed', 'string', 'value-1')); } public function testSetValueStringIsUpdatedCache(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1'); $config->setValueString('feed', 'string', 'value-2'); $status = $config->statusCache(); $this->assertSame('value-2', $status['fastCache']['feed']['string']); } public function testSetValueStringIsUpdatedDatabase(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1'); $config->setValueString('feed', 'string', 'value-2'); $config->clearCache(); $this->assertSame('value-2', $config->getValueString('feed', 'string', '')); } public function testSetValueInt(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42); $this->assertSame(42, $config->getValueInt('feed', 'int', 0)); } public function testSetValueIntCache(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42); $status = $config->statusCache(); $this->assertSame('42', $status['fastCache']['feed']['int']); } public function testSetValueIntDatabase(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42); $config->clearCache(); $this->assertSame(42, $config->getValueInt('feed', 'int', 0)); } public function testSetValueIntIsUpdated(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42); $this->assertSame(true, $config->setValueInt('feed', 'int', 17)); } public function testSetValueIntIsNotUpdated(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42); $this->assertSame(false, $config->setValueInt('feed', 'int', 42)); } public function testSetValueIntIsUpdatedCache(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42); $config->setValueInt('feed', 'int', 17); $status = $config->statusCache(); $this->assertSame('17', $status['fastCache']['feed']['int']); } public function testSetValueIntIsUpdatedDatabase(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42); $config->setValueInt('feed', 'int', 17); $config->clearCache(); $this->assertSame(17, $config->getValueInt('feed', 'int', 0)); } public function testSetValueFloat(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14); $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0)); } public function testSetValueFloatCache(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14); $status = $config->statusCache(); $this->assertSame('3.14', $status['fastCache']['feed']['float']); } public function testSetValueFloatDatabase(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14); $config->clearCache(); $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0)); } public function testSetValueFloatIsUpdated(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14); $this->assertSame(true, $config->setValueFloat('feed', 'float', 1.23)); } public function testSetValueFloatIsNotUpdated(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14); $this->assertSame(false, $config->setValueFloat('feed', 'float', 3.14)); } public function testSetValueFloatIsUpdatedCache(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14); $config->setValueFloat('feed', 'float', 1.23); $status = $config->statusCache(); $this->assertSame('1.23', $status['fastCache']['feed']['float']); } public function testSetValueFloatIsUpdatedDatabase(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14); $config->setValueFloat('feed', 'float', 1.23); $config->clearCache(); $this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0)); } public function testSetValueBool(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true); $this->assertSame(true, $config->getValueBool('feed', 'bool', false)); } public function testSetValueBoolCache(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true); $status = $config->statusCache(); $this->assertSame('1', $status['fastCache']['feed']['bool']); } public function testSetValueBoolDatabase(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true); $config->clearCache(); $this->assertSame(true, $config->getValueBool('feed', 'bool', false)); } public function testSetValueBoolIsUpdated(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true); $this->assertSame(true, $config->setValueBool('feed', 'bool', false)); } public function testSetValueBoolIsNotUpdated(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true); $this->assertSame(false, $config->setValueBool('feed', 'bool', true)); } public function testSetValueBoolIsUpdatedCache(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true); $config->setValueBool('feed', 'bool', false); $status = $config->statusCache(); $this->assertSame('0', $status['fastCache']['feed']['bool']); } public function testSetValueBoolIsUpdatedDatabase(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true); $config->setValueBool('feed', 'bool', false); $config->clearCache(); $this->assertSame(false, $config->getValueBool('feed', 'bool', true)); } public function testSetValueArray(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1]); $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [])); } public function testSetValueArrayCache(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1]); $status = $config->statusCache(); $this->assertSame('{"test":1}', $status['fastCache']['feed']['array']); } public function testSetValueArrayDatabase(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1]); $config->clearCache(); $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [])); } public function testSetValueArrayIsUpdated(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1]); $this->assertSame(true, $config->setValueArray('feed', 'array', ['test' => 2])); } public function testSetValueArrayIsNotUpdated(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1]); $this->assertSame(false, $config->setValueArray('feed', 'array', ['test' => 1])); } public function testSetValueArrayIsUpdatedCache(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1]); $config->setValueArray('feed', 'array', ['test' => 2]); $status = $config->statusCache(); $this->assertSame('{"test":2}', $status['fastCache']['feed']['array']); } public function testSetValueArrayIsUpdatedDatabase(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1]); $config->setValueArray('feed', 'array', ['test' => 2]); $config->clearCache(); $this->assertSame(['test' => 2], $config->getValueArray('feed', 'array', [])); } public function testSetLazyValueString(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', true); $this->assertSame('value-1', $config->getValueString('feed', 'string', '', true)); } public function testSetLazyValueStringCache(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', true); $status = $config->statusCache(); $this->assertSame('value-1', $status['lazyCache']['feed']['string']); } public function testSetLazyValueStringDatabase(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', true); $config->clearCache(); $this->assertSame('value-1', $config->getValueString('feed', 'string', '', true)); } public function testSetLazyValueStringAsNonLazy(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', true); $config->setValueString('feed', 'string', 'value-1', false); $this->assertSame('value-1', $config->getValueString('feed', 'string', '')); } public function testSetNonLazyValueStringAsLazy(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', false); $config->setValueString('feed', 'string', 'value-1', true); $this->assertSame('value-1', $config->getValueString('feed', 'string', '', true)); } public function testSetSensitiveValueString(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', sensitive: true); $this->assertSame('value-1', $config->getValueString('feed', 'string', '')); } public function testSetSensitiveValueStringCache(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', sensitive: true); $status = $config->statusCache(); $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['string']); } public function testSetSensitiveValueStringDatabase(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', sensitive: true); $config->clearCache(); $this->assertSame('value-1', $config->getValueString('feed', 'string', '')); } public function testSetNonSensitiveValueStringAsSensitive(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', sensitive: false); $config->setValueString('feed', 'string', 'value-1', sensitive: true); $this->assertSame(true, $config->isSensitive('feed', 'string')); $this->assertConfigValueNotEquals('feed', 'string', 'value-1'); $this->assertConfigValueNotEquals('feed', 'string', 'value-2'); } public function testSetSensitiveValueStringAsNonSensitiveStaysSensitive(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', sensitive: true); $config->setValueString('feed', 'string', 'value-2', sensitive: false); $this->assertSame(true, $config->isSensitive('feed', 'string')); $this->assertConfigValueNotEquals('feed', 'string', 'value-1'); $this->assertConfigValueNotEquals('feed', 'string', 'value-2'); } public function testSetSensitiveValueStringAsNonSensitiveAreStillUpdated(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', 'value-1', sensitive: true); $config->setValueString('feed', 'string', 'value-2', sensitive: false); $this->assertSame('value-2', $config->getValueString('feed', 'string', '')); $this->assertConfigValueNotEquals('feed', 'string', 'value-1'); $this->assertConfigValueNotEquals('feed', 'string', 'value-2'); } public function testSetLazyValueInt(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, true); $this->assertSame(42, $config->getValueInt('feed', 'int', 0, true)); } public function testSetLazyValueIntCache(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, true); $status = $config->statusCache(); $this->assertSame('42', $status['lazyCache']['feed']['int']); } public function testSetLazyValueIntDatabase(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, true); $config->clearCache(); $this->assertSame(42, $config->getValueInt('feed', 'int', 0, true)); } public function testSetLazyValueIntAsNonLazy(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, true); $config->setValueInt('feed', 'int', 42, false); $this->assertSame(42, $config->getValueInt('feed', 'int', 0)); } public function testSetNonLazyValueIntAsLazy(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, false); $config->setValueInt('feed', 'int', 42, true); $this->assertSame(42, $config->getValueInt('feed', 'int', 0, true)); } public function testSetSensitiveValueInt(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, sensitive: true); $this->assertSame(42, $config->getValueInt('feed', 'int', 0)); } public function testSetSensitiveValueIntCache(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, sensitive: true); $status = $config->statusCache(); $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['int']); } public function testSetSensitiveValueIntDatabase(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, sensitive: true); $config->clearCache(); $this->assertSame(42, $config->getValueInt('feed', 'int', 0)); } public function testSetNonSensitiveValueIntAsSensitive(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42); $config->setValueInt('feed', 'int', 42, sensitive: true); $this->assertSame(true, $config->isSensitive('feed', 'int')); } public function testSetSensitiveValueIntAsNonSensitiveStaysSensitive(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, sensitive: true); $config->setValueInt('feed', 'int', 17); $this->assertSame(true, $config->isSensitive('feed', 'int')); } public function testSetSensitiveValueIntAsNonSensitiveAreStillUpdated(): void { $config = $this->generateAppConfig(); $config->setValueInt('feed', 'int', 42, sensitive: true); $config->setValueInt('feed', 'int', 17); $this->assertSame(17, $config->getValueInt('feed', 'int', 0)); } public function testSetLazyValueFloat(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, true); $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true)); } public function testSetLazyValueFloatCache(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, true); $status = $config->statusCache(); $this->assertSame('3.14', $status['lazyCache']['feed']['float']); } public function testSetLazyValueFloatDatabase(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, true); $config->clearCache(); $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true)); } public function testSetLazyValueFloatAsNonLazy(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, true); $config->setValueFloat('feed', 'float', 3.14, false); $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0)); } public function testSetNonLazyValueFloatAsLazy(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, false); $config->setValueFloat('feed', 'float', 3.14, true); $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true)); } public function testSetSensitiveValueFloat(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, sensitive: true); $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0)); } public function testSetSensitiveValueFloatCache(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, sensitive: true); $status = $config->statusCache(); $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['float']); } public function testSetSensitiveValueFloatDatabase(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, sensitive: true); $config->clearCache(); $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0)); } public function testSetNonSensitiveValueFloatAsSensitive(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14); $config->setValueFloat('feed', 'float', 3.14, sensitive: true); $this->assertSame(true, $config->isSensitive('feed', 'float')); } public function testSetSensitiveValueFloatAsNonSensitiveStaysSensitive(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, sensitive: true); $config->setValueFloat('feed', 'float', 1.23); $this->assertSame(true, $config->isSensitive('feed', 'float')); } public function testSetSensitiveValueFloatAsNonSensitiveAreStillUpdated(): void { $config = $this->generateAppConfig(); $config->setValueFloat('feed', 'float', 3.14, sensitive: true); $config->setValueFloat('feed', 'float', 1.23); $this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0)); } public function testSetLazyValueBool(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true, true); $this->assertSame(true, $config->getValueBool('feed', 'bool', false, true)); } public function testSetLazyValueBoolCache(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true, true); $status = $config->statusCache(); $this->assertSame('1', $status['lazyCache']['feed']['bool']); } public function testSetLazyValueBoolDatabase(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true, true); $config->clearCache(); $this->assertSame(true, $config->getValueBool('feed', 'bool', false, true)); } public function testSetLazyValueBoolAsNonLazy(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true, true); $config->setValueBool('feed', 'bool', true, false); $this->assertSame(true, $config->getValueBool('feed', 'bool', false)); } public function testSetNonLazyValueBoolAsLazy(): void { $config = $this->generateAppConfig(); $config->setValueBool('feed', 'bool', true, false); $config->setValueBool('feed', 'bool', true, true); $this->assertSame(true, $config->getValueBool('feed', 'bool', false, true)); } public function testSetLazyValueArray(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], true); $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true)); } public function testSetLazyValueArrayCache(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], true); $status = $config->statusCache(); $this->assertSame('{"test":1}', $status['lazyCache']['feed']['array']); } public function testSetLazyValueArrayDatabase(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], true); $config->clearCache(); $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true)); } public function testSetLazyValueArrayAsNonLazy(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], true); $config->setValueArray('feed', 'array', ['test' => 1], false); $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [])); } public function testSetNonLazyValueArrayAsLazy(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], false); $config->setValueArray('feed', 'array', ['test' => 1], true); $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true)); } public function testSetSensitiveValueArray(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true); $this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', [])); } public function testSetSensitiveValueArrayCache(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true); $status = $config->statusCache(); $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['array']); } public function testSetSensitiveValueArrayDatabase(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true); $config->clearCache(); $this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', [])); } public function testSetNonSensitiveValueArrayAsSensitive(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1]); $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true); $this->assertSame(true, $config->isSensitive('feed', 'array')); } public function testSetSensitiveValueArrayAsNonSensitiveStaysSensitive(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true); $config->setValueArray('feed', 'array', ['test' => 2]); $this->assertSame(true, $config->isSensitive('feed', 'array')); } public function testSetSensitiveValueArrayAsNonSensitiveAreStillUpdated(): void { $config = $this->generateAppConfig(); $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true); $config->setValueArray('feed', 'array', ['test' => 2]); $this->assertEqualsCanonicalizing(['test' => 2], $config->getValueArray('feed', 'array', [])); } public function testUpdateNotSensitiveToSensitive(): void { $config = $this->generateAppConfig(); $config->updateSensitive('non-sensitive-app', 'lazy-key', true); $this->assertSame(true, $config->isSensitive('non-sensitive-app', 'lazy-key', true)); } public function testUpdateSensitiveToNotSensitive(): void { $config = $this->generateAppConfig(); $config->updateSensitive('sensitive-app', 'lazy-key', false); $this->assertSame(false, $config->isSensitive('sensitive-app', 'lazy-key', true)); } public function testUpdateSensitiveToSensitiveReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->updateSensitive('sensitive-app', 'lazy-key', true)); } public function testUpdateNotSensitiveToNotSensitiveReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'lazy-key', false)); } public function testUpdateSensitiveOnUnknownKeyReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'unknown-key', true)); } public function testUpdateNotLazyToLazy(): void { $config = $this->generateAppConfig(); $config->updateLazy('non-sensitive-app', 'non-lazy-key', true); $this->assertSame(true, $config->isLazy('non-sensitive-app', 'non-lazy-key')); } public function testUpdateLazyToNotLazy(): void { $config = $this->generateAppConfig(); $config->updateLazy('non-sensitive-app', 'lazy-key', false); $this->assertSame(false, $config->isLazy('non-sensitive-app', 'lazy-key')); } public function testUpdateLazyToLazyReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->updateLazy('non-sensitive-app', 'lazy-key', true)); } public function testUpdateNotLazyToNotLazyReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->updateLazy('non-sensitive-app', 'non-lazy-key', false)); } public function testUpdateLazyOnUnknownKeyReturnsFalse(): void { $config = $this->generateAppConfig(); $this->assertSame(false, $config->updateLazy('non-sensitive-app', 'unknown-key', true)); } public function testGetDetails(): void { $config = $this->generateAppConfig(); $this->assertEquals( [ 'app' => 'non-sensitive-app', 'key' => 'lazy-key', 'value' => 'value', 'type' => 4, 'lazy' => true, 'typeString' => 'string', 'sensitive' => false, ], $config->getDetails('non-sensitive-app', 'lazy-key') ); } public function testGetDetailsSensitive(): void { $config = $this->generateAppConfig(); $this->assertEquals( [ 'app' => 'sensitive-app', 'key' => 'lazy-key', 'value' => 'value', 'type' => 4, 'lazy' => true, 'typeString' => 'string', 'sensitive' => true, ], $config->getDetails('sensitive-app', 'lazy-key') ); } public function testGetDetailsInt(): void { $config = $this->generateAppConfig(); $this->assertEquals( [ 'app' => 'typed', 'key' => 'int', 'value' => '42', 'type' => 8, 'lazy' => false, 'typeString' => 'integer', 'sensitive' => false ], $config->getDetails('typed', 'int') ); } public function testGetDetailsFloat(): void { $config = $this->generateAppConfig(); $this->assertEquals( [ 'app' => 'typed', 'key' => 'float', 'value' => '3.14', 'type' => 16, 'lazy' => false, 'typeString' => 'float', 'sensitive' => false ], $config->getDetails('typed', 'float') ); } public function testGetDetailsBool(): void { $config = $this->generateAppConfig(); $this->assertEquals( [ 'app' => 'typed', 'key' => 'bool', 'value' => '1', 'type' => 32, 'lazy' => false, 'typeString' => 'boolean', 'sensitive' => false ], $config->getDetails('typed', 'bool') ); } public function testGetDetailsArray(): void { $config = $this->generateAppConfig(); $this->assertEquals( [ 'app' => 'typed', 'key' => 'array', 'value' => '{"test": 1}', 'type' => 64, 'lazy' => false, 'typeString' => 'array', 'sensitive' => false ], $config->getDetails('typed', 'array') ); } public function testDeleteKey(): void { $config = $this->generateAppConfig(); $config->deleteKey('anotherapp', 'key'); $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default')); } public function testDeleteKeyCache(): void { $config = $this->generateAppConfig(); $config->deleteKey('anotherapp', 'key'); $status = $config->statusCache(); $this->assertEqualsCanonicalizing(['enabled' => 'false'], $status['fastCache']['anotherapp']); } public function testDeleteKeyDatabase(): void { $config = $this->generateAppConfig(); $config->deleteKey('anotherapp', 'key'); $config->clearCache(); $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default')); } public function testDeleteApp(): void { $config = $this->generateAppConfig(); $config->deleteApp('anotherapp'); $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default')); $this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default')); } public function testDeleteAppCache(): void { $config = $this->generateAppConfig(); $status = $config->statusCache(); $this->assertSame(true, isset($status['fastCache']['anotherapp'])); $config->deleteApp('anotherapp'); $status = $config->statusCache(); $this->assertSame(false, isset($status['fastCache']['anotherapp'])); } public function testDeleteAppDatabase(): void { $config = $this->generateAppConfig(); $config->deleteApp('anotherapp'); $config->clearCache(); $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default')); $this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default')); } public function testClearCache(): void { $config = $this->generateAppConfig(); $config->setValueString('feed', 'string', '123454'); $config->clearCache(); $status = $config->statusCache(); $this->assertSame([], $status['fastCache']); } public function testSensitiveValuesAreEncrypted(): void { $key = self::getUniqueID('secret'); $appConfig = $this->generateAppConfig(); $secret = md5((string) time()); $appConfig->setValueString('testapp', $key, $secret, sensitive: true); $this->assertConfigValueNotEquals('testapp', $key, $secret); // Can get in same run $actualSecret = $appConfig->getValueString('testapp', $key); $this->assertEquals($secret, $actualSecret); // Can get freshly decrypted from DB $newAppConfig = $this->generateAppConfig(); $actualSecret = $newAppConfig->getValueString('testapp', $key); $this->assertEquals($secret, $actualSecret); } public function testMigratingNonSensitiveValueToSensitiveWithSetValue(): void { $key = self::getUniqueID('secret'); $appConfig = $this->generateAppConfig(); $secret = sha1((string) time()); // Unencrypted $appConfig->setValueString('testapp', $key, $secret); $this->assertConfigKey('testapp', $key, $secret); // Can get freshly decrypted from DB $newAppConfig = $this->generateAppConfig(); $actualSecret = $newAppConfig->getValueString('testapp', $key); $this->assertEquals($secret, $actualSecret); // Encrypting on change $appConfig->setValueString('testapp', $key, $secret, sensitive: true); $this->assertConfigValueNotEquals('testapp', $key, $secret); // Can get in same run $actualSecret = $appConfig->getValueString('testapp', $key); $this->assertEquals($secret, $actualSecret); // Can get freshly decrypted from DB $newAppConfig = $this->generateAppConfig(); $actualSecret = $newAppConfig->getValueString('testapp', $key); $this->assertEquals($secret, $actualSecret); } public function testUpdateSensitiveValueToNonSensitiveWithUpdateSensitive(): void { $key = self::getUniqueID('secret'); $appConfig = $this->generateAppConfig(); $secret = sha1((string) time()); // Encrypted $appConfig->setValueString('testapp', $key, $secret, sensitive: true); $this->assertConfigValueNotEquals('testapp', $key, $secret); // Migrate to non-sensitive / non-encrypted $appConfig->updateSensitive('testapp', $key, false); $this->assertConfigKey('testapp', $key, $secret); } public function testUpdateNonSensitiveValueToSensitiveWithUpdateSensitive(): void { $key = self::getUniqueID('secret'); $appConfig = $this->generateAppConfig(); $secret = sha1((string) time()); // Unencrypted $appConfig->setValueString('testapp', $key, $secret); $this->assertConfigKey('testapp', $key, $secret); // Migrate to sensitive / encrypted $appConfig->updateSensitive('testapp', $key, true); $this->assertConfigValueNotEquals('testapp', $key, $secret); } protected function loadConfigValueFromDatabase(string $app, string $key): string|false { $sql = $this->connection->getQueryBuilder(); $sql->select('configvalue') ->from('appconfig') ->where($sql->expr()->eq('appid', $sql->createParameter('appid'))) ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey'))) ->setParameter('appid', $app) ->setParameter('configkey', $key); $query = $sql->executeQuery(); $actual = $query->fetchOne(); $query->closeCursor(); return $actual; } protected function assertConfigKey(string $app, string $key, string|false $expected): void { $this->assertEquals($expected, $this->loadConfigValueFromDatabase($app, $key)); } protected function assertConfigValueNotEquals(string $app, string $key, string|false $expected): void { $this->assertNotEquals($expected, $this->loadConfigValueFromDatabase($app, $key)); } }