snapshot of https://github.com/espocrm/espocrm.git (master, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Test / Test on PHP 8.3 (push) Failing after 5s
Test / Test on PHP 8.4 (push) Failing after 4s
Test / Test on PHP 8.5 (push) Failing after 3s
TypeScript static analysis / TypeScript check (push) Failing after 14s
Test Integration on MySQL / Test on PHP 8.3 (push) Failing after 2s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 0s
Test Integration on PostgreSQL / Test on PHP 8.4 fix (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 master (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-18 04:19:15 +05:00
commit 7cc7e6b4f3
10077 changed files with 900756 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\Autoload;
use Espo\Core\Utils\Autoload\Loader;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Resource\PathProvider;
use PHPUnit\Framework\TestCase;
class AutoloadTest extends TestCase
{
private ?Config\SystemConfig $systemConfig = null;
private $metadata;
private $fileManager;
private $loader;
private $pathProvider;
private $autoload;
protected function setUp(): void
{
$this->systemConfig = $this->createMock(Config\SystemConfig::class);
$this->metadata = $this->createMock(Metadata::class);
$dataCache = $this->createMock(DataCache::class);
$this->fileManager = $this->createMock(FileManager::class);
$this->loader = $this->createMock(Loader::class);
$this->pathProvider = $this->createMock(PathProvider::class);
$this->initPathProvider();
$this->autoload = new Autoload(
$this->metadata,
$dataCache,
$this->fileManager,
$this->loader,
$this->pathProvider,
$this->systemConfig,
);
}
private function initPathProvider(string $rootPath = ''): void
{
$this->pathProvider
->method('getCustom')
->willReturn($rootPath . 'custom/Espo/Custom/Resources/');
$this->pathProvider
->method('getCore')
->willReturn($rootPath . 'application/Espo/Resources/');
$this->pathProvider
->method('getModule')
->willReturnCallback(
function (?string $moduleName) use ($rootPath): string {
$path = $rootPath . 'application/Espo/Modules/{*}/Resources/';
if ($moduleName === null) {
return $path;
}
return str_replace('{*}', $moduleName, $path);
}
);
}
public function testMerge()
{
$this->metadata
->expects($this->once())
->method('getModuleList')
->willReturn(['M1', 'M2']);
$this->systemConfig
->expects($this->once())
->method('useCache')
->willReturn(false);
$this->fileManager
->expects($this->any())
->method('isFile')
->willReturnMap(
[
['application/Espo/Resources/autoload.json', false],
['application/Espo/Modules/M1/Resources/autoload.json', true],
['application/Espo/Modules/M2/Resources/autoload.json', true],
['custom/Espo/Custom/Resources/autoload.json', false],
]
);
$data1 = [
'autoloadFileList' => ['f1.php'],
'psr-4' => [
't1' => 'r1',
],
];
$data2 = [
'autoloadFileList' => ['f2.php'],
'psr-4' => [
't2' => 'r2',
],
];
$expectedData = [
'autoloadFileList' => ['f1.php', 'f2.php'],
'psr-4' => [
't1' => 'r1',
't2' => 'r2',
],
];
$this->fileManager
->expects($this->any())
->method('getContents')
->willReturnMap(
[
['application/Espo/Modules/M1/Resources/autoload.json', json_encode($data1)],
['application/Espo/Modules/M2/Resources/autoload.json', json_encode($data2)],
]
);
$this->loader
->expects($this->once())
->method('register')
->with($expectedData);
$this->autoload->register();
}
}

View File

@@ -0,0 +1,370 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Config;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Config\ConfigWriter;
use Espo\Core\Utils\Config\ConfigWriterFileManager;
use Espo\Core\Utils\Config\ConfigWriterHelper;
use Espo\Core\Utils\Config\InternalConfigHelper;
use PHPUnit\Framework\TestCase;
class ConfigWriterTest extends TestCase
{
private $fileManager;
private $config;
private $helper;
private $internalConfigHelper;
private $configWriter;
private $configPath;
private $internalConfigPath;
private $stateConfigPath;
protected function setUp(): void
{
$this->fileManager = $this->createMock(ConfigWriterFileManager::class);
$this->config = $this->createMock(Config::class);
$this->helper = $this->createMock(ConfigWriterHelper::class);
$this->internalConfigHelper = $this->createMock(InternalConfigHelper::class);
$this->configWriter = new ConfigWriter(
$this->config,
$this->fileManager,
$this->helper,
$this->internalConfigHelper
);
$this->configPath = 'somePath';
$this->internalConfigPath = 'internalSomePath';
$this->stateConfigPath = 'stateSomePath';
$this->config
->expects($this->any())
->method('getConfigPath')
->willReturn($this->configPath);
$this->config
->expects($this->any())
->method('getInternalConfigPath')
->willReturn($this->internalConfigPath);
$this->config
->expects($this->any())
->method('getStateConfigPath')
->willReturn($this->stateConfigPath);
}
public function testSave1(): void
{
$this->configWriter->set('k1', 'v1');
$this->configWriter->setMultiple([
'k2' => 'v2',
'k3' => 'v3',
]);
$this->configWriter->remove('k4');
$previousData = [
'k3' => 'e3',
'k4' => 'e4',
'microtime' => 0.0,
'cacheTimestamp' => 0,
];
$newData = [
'k1' => 'v1',
'k2' => 'v2',
'k3' => 'v3',
'microtime' => 1.0,
'cacheTimestamp' => 1,
];
$this->helper
->expects($this->once())
->method('generateMicrotime')
->willReturn(1.0);
$this->helper
->expects($this->once())
->method('generateCacheTimestamp')
->willReturn(1);
$this->config
->expects($this->once())
->method('update');
$this->fileManager
->expects($this->any())
->method('isFile')
->willReturnMap([
[$this->configPath, true],
[$this->internalConfigPath, false],
[$this->stateConfigPath, false],
]);
$this->fileManager
->expects($this->once())
->method('putPhpContents')
->with($this->configPath, $newData);
$this->fileManager
->expects($this->exactly(2))
->method('getPhpContents')
->willReturnMap([
[$this->configPath, $previousData],
[$this->configPath, $previousData],
]);
$this->configWriter->save();
}
public function testSave2(): void
{
$this->configWriter->set('k1', 'v1');
$this->configWriter->set('k2', 'v2');
$this->internalConfigHelper
->expects($this->any())
->method('isParamForInternalConfig')
->willReturnMap(
[
['k1', false],
['k2', true],
['cacheTimestamp', false],
]
);
$this->internalConfigHelper
->expects($this->any())
->method('isParamForStateConfig')
->willReturnMap(
[
['k1', false],
['k2', false],
['cacheTimestamp', true],
]
);
$this->helper
->expects($this->exactly(3))
->method('generateMicrotime')
->willReturn(1.0);
$this->helper
->expects($this->once())
->method('generateCacheTimestamp')
->willReturn(1);
$this->fileManager
->expects(self::any())
->method('isFile')
->willReturnMap([
[$this->configPath, true],
[$this->internalConfigPath, true],
[$this->stateConfigPath, true],
]);
$this->fileManager
->expects($this->exactly(6))
->method('getPhpContents')
->willReturnMap([
[$this->configPath, []],
[$this->internalConfigPath, []],
[$this->stateConfigPath, []],
]);
$this->fileManager
->expects(self::any())
->method('putPhpContents')
->willReturnMap([
[
$this->internalConfigPath,
['k2' => 'v2', 'microtimeInternal' => 1.0]
],
[
$this->configPath,
['k1' => 'v1', 'microtime' => 1.0]
],
[
$this->stateConfigPath,
['cacheTimestamp' => 1, 'microtimeState' => 1.0]
],
]);
$this->configWriter->save();
}
public function testSave3(): void
{
$this->configWriter->set('k1', 'v1');
$this->configWriter->set('k2', 'v2');
$this->internalConfigHelper
->expects($this->any())
->method('isParamForInternalConfig')
->willReturnMap(
[
['k1', false],
['k2', true],
['cacheTimestamp', false],
]
);
$this->internalConfigHelper
->expects($this->any())
->method('isParamForStateConfig')
->willReturnMap(
[
['k1', false],
['k2', false],
['cacheTimestamp', true],
]
);
$this->helper
->expects($this->exactly(3))
->method('generateMicrotime')
->willReturn(1.0);
$this->helper
->expects($this->once())
->method('generateCacheTimestamp')
->willReturn(1);
$this->fileManager
->expects(self::any())
->method('isFile')
->willReturnMap([
[$this->configPath, true],
[$this->internalConfigPath, true],
[$this->stateConfigPath, true],
]);
$this->fileManager
->expects($this->exactly(6))
->method('getPhpContents')
->willReturnMap([
[$this->configPath, []],
[$this->internalConfigPath, []],
[$this->stateConfigPath, []],
]);
$this->fileManager
->expects(self::any())
->method('putPhpContents')
->willReturnMap([
[
$this->internalConfigPath,
['k2' => 'v2', 'microtimeInternal' => 1.0]
],
[
$this->configPath,
['k1' => 'v1', 'microtime' => 1.0]
],
[
$this->stateConfigPath,
['cacheTimestamp' => 1, 'microtimeState' => 1.0]
],
]);
$this->configWriter->save();
}
public function testsRemove1(): void
{
$this->configWriter->remove('k1');
$this->internalConfigHelper
->expects($this->any())
->method('isParamForInternalConfig')
->willReturnMap(
[
['k1', false],
['cacheTimestamp', false],
]
);
$this->internalConfigHelper
->expects($this->any())
->method('isParamForStateConfig')
->willReturnMap(
[
['k1', false],
['cacheTimestamp', false],
]
);
$this->helper
->expects($this->once())
->method('generateCacheTimestamp')
->willReturn(1);
$this->fileManager
->expects(self::any())
->method('isFile')
->willReturnMap([
[$this->configPath, true],
[$this->internalConfigPath, true],
[$this->stateConfigPath, true],
]);
$this->fileManager
->expects(self::any())
->method('isFile')
->willReturnMap([
[$this->configPath, true],
[$this->internalConfigPath, true],
[$this->stateConfigPath, true],
]);
$this->fileManager
->expects($this->exactly(4))
->method('getPhpContents')
->willReturnMap([
[$this->configPath, ['k1' => 'v1', 'k2' => 'v2']],
[$this->internalConfigPath, []],
[$this->stateConfigPath, []],
]);
$this->fileManager
->expects($this->exactly(1))
->method('putPhpContents')
->willReturnMap([
[
$this->configPath,
['k2' => 'v2', 'microtime' => 1.0]
],
]);
$this->configWriter->save();
}
}

View File

@@ -0,0 +1,303 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use PHPUnit\Framework\TestCase;
use tests\unit\ReflectionHelper;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Config\ConfigFileManager;
class ConfigTest extends TestCase
{
private ?Config $config = null;
private $defaultTestConfig = 'tests/unit/testData/Utils/Config/config.php';
private $configPath = 'tests/unit/testData/cache/config.php';
private $systemConfigPath = 'tests/unit/testData/Utils/Config/systemConfig.php';
private $internalConfigPath = 'tests/unit/testData/cache/config-internal.php';
private $stateConfigPath = 'tests/unit/testData/cache/state.php';
private $reflection;
private $fileManager;
protected function setUp(): void
{
$this->fileManager = new ConfigFileManager;
/*copy defaultTestConfig file to cache*/
if (!file_exists($this->configPath)) {
copy($this->defaultTestConfig, $this->configPath);
}
$this->config = new Config($this->fileManager);
$this->reflection = new ReflectionHelper($this->config);
$this->reflection->setProperty('configPath', $this->configPath);
$this->reflection->setProperty('systemConfigPath', $this->systemConfigPath);
$this->reflection->setProperty('internalConfigPath', $this->internalConfigPath);
$this->reflection->setProperty('stateConfigPath', $this->stateConfigPath);
}
public function testLoadConfig()
{
$this->assertArrayHasKey('database', $this->reflection->invokeMethod('getData', []));
$this->assertArrayHasKey('dateFormat', $this->reflection->invokeMethod('getData', []));
}
public function testGet1(): void
{
$result = [
'driver' => 'pdo_mysql',
'host' => 'localhost',
'dbname' => 'espocrm',
'user' => 'root',
'password' => '',
];
$this->assertEquals($result, $this->config->get('database'));
$result = 'pdo_mysql';
$this->assertEquals($result, $this->config->get('database.driver'));
$result = 'YYYY-MM-DD';
$this->assertEquals($result, $this->config->get('dateFormat'));
$this->assertTrue($this->config->get('isInstalled'));
}
public function testSystemConfigMerge()
{
$configDataWithoutSystem = $this->fileManager->getPhpContents($this->configPath);
$this->assertArrayNotHasKey('systemItems', $configDataWithoutSystem);
$this->assertArrayNotHasKey('adminItems', $configDataWithoutSystem);
$configData = $this->reflection->invokeMethod('getData', []);
$this->assertArrayHasKey('systemItems', $configData);
$this->assertArrayHasKey('adminItems', $configData);
}
public function testGet2(): void
{
$fileManager = $this->createMock(ConfigFileManager::class);
$fileManager
->method('isFile')
->willReturnMap(
[
['data/config.php', true],
['data/config-internal.php', true],
['application/Espo/Resources/defaults/systemConfig.php', true],
['data/config-override.php', false],
['data/state.php', true],
['data/config-internal-override.php', false],
]
);
$data = [
'test1' => '1',
];
$dataInternal = [
'test2' => '2',
];
$dataSystem = [
'test3' => '3',
];
$fileManager
->method('getPhpContents')
->willReturnMap(
[
['data/config.php', $data],
['data/config-internal.php', $dataInternal],
['data/state.php', []],
['application/Espo/Resources/defaults/systemConfig.php', $dataSystem],
]
);
$config = new Config($fileManager);
$this->assertEquals('1', $config->get('test1'));
$this->assertEquals('2', $config->get('test2'));
$this->assertEquals('3', $config->get('test3'));
$this->assertFalse($config->isInternal('test1'));
$this->assertTrue($config->isInternal('test2'));
$this->assertFalse($config->isInternal('test3'));
$this->assertEquals(
(object) [
'test1' => '1',
'test3' => '3',
],
$config->getAllNonInternalData(),
);
}
public function testGet3(): void
{
$fileManager = $this->createMock(ConfigFileManager::class);
$fileManager
->method('isFile')
->willReturnMap(
[
['data/config.php', true],
['data/config-internal.php', true],
['application/Espo/Resources/defaults/systemConfig.php', true],
['data/config-override.php', false],
['data/state.php', true],
['data/config-internal-override.php', false],
]
);
$data = [
'a' => [
'1' => 'a1',
],
'b' => (object) [
'1' => 'b1',
],
'c' => 'c',
'd' => ['d'],
];
$dataInternal = [
];
$dataSystem = [
];
$fileManager
->method('getPhpContents')
->willReturnMap(
[
['data/config.php', $data],
['data/config-internal.php', $dataInternal],
['data/state.php', []],
['application/Espo/Resources/defaults/systemConfig.php', $dataSystem],
]
);
$config = new Config($fileManager);
$this->assertEquals(['1' => 'a1'], $config->get('a'));
$this->assertEquals('a1', $config->get('a.1'));
$this->assertEquals('b1', $config->get('b.1'));
$this->assertEquals('c', $config->get('c'));
$this->assertEquals(['d'], $config->get('d'));
$this->assertFalse($config->has('a.2'));
$this->assertTrue($config->has('a.1'));
$this->assertTrue($config->has('a'));
$this->assertFalse($config->has('0'));
}
public function testGetWithOverride(): void
{
$fileManager = $this->createMock(ConfigFileManager::class);
$fileManager
->method('isFile')
->willReturnMap(
[
['application/Espo/Resources/defaults/systemConfig.php', true],
['data/config.php', true],
['data/config-internal.php', true],
['data/config-override.php', true],
['data/state.php', true],
['data/config-internal-override.php', true],
]
);
$dataSystem = [];
$data = [
'a' => 'a',
'c' => 'c',
'b' => 'b0',
];
$dataInternal = [
'b' => 'b0',
'e' => 'e0',
];
$dataOverride = [
'c' => 'c1',
];
$dataInternalOverride = [
'e' => 'e1',
'h' => 'h1',
];
$fileManager
->method('getPhpContents')
->willReturnMap(
[
['application/Espo/Resources/defaults/systemConfig.php', $dataSystem],
['data/config.php', $data],
['data/config-internal.php', $dataInternal],
['data/config-override.php', $dataOverride],
['data/state.php', []],
['data/config-internal-override.php', $dataInternalOverride],
]
);
$config = new Config($fileManager);
$this->assertEquals('a', $config->get('a'));
$this->assertEquals('c1', $config->get('c'));
$this->assertEquals('e1', $config->get('e'));
$this->assertEquals('h1', $config->get('h'));
$this->assertFalse($config->isInternal('c'));
$this->assertTrue($config->isInternal('e'));
$this->assertTrue($config->isInternal('h'));
$this->assertTrue($config->isInternal('b'));
$nonInternalData = $config->getAllNonInternalData();
$this->assertTrue(isset($nonInternalData->a));
$this->assertTrue(isset($nonInternalData->c));
$this->assertFalse(isset($nonInternalData->e));
$this->assertFalse(isset($nonInternalData->h));
}
}

View File

@@ -0,0 +1,169 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\Cache\Exceptions\InvalidArgument;
use Espo\Core\Utils\Cache\FileCacheItemPool;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\File\Manager as FileManager;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use InvalidArgumentException;
class DataCacheTest extends TestCase
{
private ?FileManager $fileManager = null;
private ?DataCache $dataCache = null;
protected function setUp() : void
{
$this->fileManager = $this->createMock(FileManager::class);
$pool = new FileCacheItemPool($this->fileManager);
$this->dataCache = new DataCache($pool);
}
public function testHasTrue()
{
$this->fileManager
->expects($this->once())
->method('isFile')
->with('data/cache/application/autoload.php')
->willReturn(true);
$result = $this->dataCache->has('autoload');
$this->assertTrue($result);
}
public function testHasFalse()
{
$this->fileManager
->expects($this->once())
->method('isFile')
->with('data/cache/application/autoload.php')
->willReturn(false);
$result = $this->dataCache->has('autoload');
$this->assertFalse($result);
}
public function testStoreData()
{
$data = [
'test' => 1,
];
$this->fileManager
->expects($this->once())
->method('putPhpContents')
->with('data/cache/application/autoload.php', $data, true, true)
->willReturn(true);
$this->dataCache->store('autoload', $data);
}
public function testStoreError()
{
$this->expectException(RuntimeException::class);
$data = [
'test' => 1,
];
$this->fileManager
->expects($this->once())
->method('putPhpContents')
->with('data/cache/application/autoload.php', $data, true, true)
->willReturn(false);
$this->dataCache->store('autoload', $data);
}
public function testStoreBadDataType()
{
$this->expectException(InvalidArgument::class);
$data = false;
$this->dataCache->store('autoload', $data);
}
public function testSubDir()
{
$this->fileManager
->expects($this->once())
->method('isFile')
->with('data/cache/application/languageTest/test0.php')
->willReturn(true);
$result = $this->dataCache->has('languageTest/test0');
$this->assertTrue($result);
}
public function testBadKey1()
{
$this->expectException(InvalidArgumentException::class);
$result = $this->dataCache->has('/language');
$this->assertTrue($result);
}
public function testBadKey2()
{
$this->expectException(InvalidArgumentException::class);
$result = $this->dataCache->has('language/');
$this->assertTrue($result);
}
public function testBadKey3()
{
$this->expectException(InvalidArgumentException::class);
$result = $this->dataCache->has('');
$this->assertTrue($result);
}
public function testBadKey4()
{
$this->expectException(InvalidArgumentException::class);
$result = $this->dataCache->has('language\test');
$this->assertTrue($result);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Database\Orm\Defs;
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
use Espo\ORM\Type\AttributeType;
use PHPUnit\Framework\TestCase;
class AttributeDefsTest extends TestCase
{
public function testWithParamsMerged(): void
{
$defs = AttributeDefs::create('test')
->withParamsMerged([
'a' => 'a',
'b' => 'b',
])
->withParamsMerged([
'b' => 'mb',
'c' => 'mc',
])
->withParam('e', 'e');
$this->assertEquals('a', $defs->getParam('a'));
$this->assertEquals('mb', $defs->getParam('b'));
$this->assertEquals('mc', $defs->getParam('c'));
$this->assertEquals('e', $defs->getParam('e'));
$this->assertTrue($defs->hasParam('c'));
$this->assertFalse($defs->hasParam('d'));
}
public function testToAssoc(): void
{
$params = [
'a' => 'a',
'b' => 'b',
];
$defs = AttributeDefs::create('test')
->withParamsMerged($params);
$this->assertEquals($params, $defs->toAssoc());
}
public function testType(): void
{
$defs = AttributeDefs::create('test')->withType(AttributeType::VARCHAR);
$this->assertEquals(AttributeType::VARCHAR, $defs->getType());
}
public function testParams(): void
{
$defs = AttributeDefs::create('test')
->withNotStorable()
->withDefault('1');
$this->assertEquals([
'notStorable' => true,
'default' => '1',
], $defs->toAssoc());
}
}

View File

@@ -0,0 +1,70 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Database\Orm\Defs;
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
use Espo\Core\Utils\Database\Orm\Defs\EntityDefs;
use Espo\Core\Utils\Database\Orm\Defs\IndexDefs;
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
use Espo\ORM\Type\AttributeType;
use Espo\ORM\Type\RelationType;
use PHPUnit\Framework\TestCase;
class EntityDefsTest extends TestCase
{
public function testWith(): void
{
$entityDefs = EntityDefs::create();
$a1 = AttributeDefs::create('a1')->withType(AttributeType::VARCHAR);
$a2 = AttributeDefs::create('a2')->withType(AttributeType::INT);
$r1 = RelationDefs::create('r1')->withType(RelationType::MANY_MANY);
$i1 = IndexDefs::create('i1')->withParam('key', 'KEY_1');
$entityDefs = $entityDefs
->withAttribute($a1)
->withAttribute($a2)
->withRelation($r1)
->withIndex($i1);
$this->assertEquals([
'attributes' => [
'a1' => $a1->toAssoc(),
'a2' => $a2->toAssoc(),
],
'relations' => [
'r1' => $r1->toAssoc(),
],
'indexes' => [
'i1' => $i1->toAssoc(),
],
], $entityDefs->toAssoc());
}
}

View File

@@ -0,0 +1,97 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Database\Orm\Defs;
use Espo\Core\Utils\Database\Orm\Defs\IndexDefs;
use PHPUnit\Framework\TestCase;
class IndexDefsTest extends TestCase
{
public function testWithParamsMerged(): void
{
$defs = IndexDefs::create('test')
->withParamsMerged([
'a' => 'a',
'b' => 'b',
])
->withParamsMerged([
'b' => 'mb',
'c' => 'mc',
])
->withParam('e', 'e');
$this->assertEquals('a', $defs->getParam('a'));
$this->assertEquals('mb', $defs->getParam('b'));
$this->assertEquals('mc', $defs->getParam('c'));
$this->assertEquals('e', $defs->getParam('e'));
$this->assertTrue($defs->hasParam('c'));
$this->assertFalse($defs->hasParam('d'));
}
public function testToAssoc(): void
{
$params = [
'a' => 'a',
'b' => 'b',
];
$defs = IndexDefs::create('test')
->withParamsMerged($params);
$this->assertEquals($params, $defs->toAssoc());
}
public function testUnique(): void
{
$defs = IndexDefs::create('test')
->withUnique();
$this->assertEquals(['type' => 'unique'], $defs->toAssoc());
$defs = IndexDefs::create('test')
->withoutUnique();
$this->assertEquals([], $defs->toAssoc());
}
public function testWithFlag(): void
{
$defs = IndexDefs::create('test')
->withFlag('fulltext');
$this->assertEquals(['flags' => ['fulltext']], $defs->toAssoc());
$defs = IndexDefs::create('test')
->withoutFlag('fulltext');
$this->assertEquals([], $defs->toAssoc());
}
}

View File

@@ -0,0 +1,117 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Database\Orm\Defs;
use Espo\Core\Utils\Database\Orm\Defs\AttributeDefs;
use Espo\Core\Utils\Database\Orm\Defs\RelationDefs;
use Espo\ORM\Type\AttributeType;
use Espo\ORM\Type\RelationType;
use PHPUnit\Framework\TestCase;
class RelationDefsTest extends TestCase
{
public function testWithParamsMerged(): void
{
$defs = RelationDefs::create('test')
->withParamsMerged([
'a' => 'a',
'b' => 'b',
])
->withParamsMerged([
'b' => 'mb',
'c' => 'mc',
])
->withParam('e', 'e');
$this->assertEquals('a', $defs->getParam('a'));
$this->assertEquals('mb', $defs->getParam('b'));
$this->assertEquals('mc', $defs->getParam('c'));
$this->assertEquals('e', $defs->getParam('e'));
$this->assertTrue($defs->hasParam('c'));
$this->assertFalse($defs->hasParam('d'));
}
public function testToAssoc(): void
{
$params = [
'a' => 'a',
'b' => 'b',
];
$defs = RelationDefs::create('test')
->withParamsMerged($params);
$this->assertEquals($params, $defs->toAssoc());
}
public function testParams(): void
{
$defs = RelationDefs::create('test')
->withType(RelationType::MANY_MANY)
->withForeignEntityType('Test')
->withForeignRelationName('foreign')
->withRelationshipName('Name')
->withKey('key')
->withForeignKey('foreignKey')
->withMidKeys('k1', 'k2')
->withAdditionalColumn(
AttributeDefs::create('entityType')
->withType(AttributeType::VARCHAR)
->withLength(100)
)
->withAdditionalColumn(
AttributeDefs::create('primary')
->withType(AttributeType::BOOL)
->withDefault(false)
);
$this->assertEquals(RelationType::MANY_MANY, $defs->getType());
$this->assertEquals('Test', $defs->getForeignEntityType());
$this->assertEquals('foreign', $defs->getForeignRelationName());
$this->assertEquals('Name', $defs->getRelationshipName());
$this->assertEquals('key', $defs->getKey());
$this->assertEquals('foreignKey', $defs->getForeignKey());
$this->assertEquals(['k1', 'k2'], $defs->getParam('midKeys'));
$params = $defs->toAssoc();
$this->assertEquals([
'entityType' => [
'type' => AttributeType::VARCHAR,
'len' => 100,
],
'primary' => [
'type' => AttributeType::BOOL,
'default' => false,
],
], $params['additionalColumns']);
}
}

View File

@@ -0,0 +1,105 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\DateTime;
class DateTimeTest extends \PHPUnit\Framework\TestCase
{
protected function setUp(): void
{
date_default_timezone_set('UTC');
}
public function testConvertFormat(): void
{
$map = [
'YYYY-MM-DD' => 'Y-m-d',
'DD-MM-YYYY' => 'd-m-Y',
'MM-DD-YYYY' => 'm-d-Y',
'MM/DD/YYYY' => 'm/d/Y',
'DD/MM/YYYY' => 'd/m/Y',
'DD.MM.YYYY' => 'd.m.Y',
'DD. MM. YYYY' => 'd. m. Y',
'MM.DD.YYYY' => 'm.d.Y',
'YYYY.MM.DD' => 'Y.m.d',
'HH:mm' => 'H:i',
'HH:mm:ss' => 'H:i:s',
'hh:mm a' => 'h:i a',
'hh:mma' => 'h:ia',
'hh:mm A' => 'h:i A',
'hh:mmA' => 'h:iA',
'DD. MM. YYYY HH:mm' => 'd. m. Y H:i',
];
foreach ($map as $from => $to) {
$this->assertEquals($to, DateTime::convertFormatToSystem($from));
}
}
public function testConvertGetFormat(): void
{
$util = new DateTime('YYYY-MM-DD', 'HH:mm', 'Europe/Kyiv');
$this->assertEquals('YYYY-MM-DD HH:mm', $util->getDateTimeFormat());
$this->assertEquals('YYYY-MM-DD', $util->getDateFormat());
}
public function testConvertSystemDateTime1(): void
{
$util = new DateTime('DD-MM-YYYY', 'HH:mm', 'Europe/Kyiv');
$this->assertEquals(
'20-05-2021 13:00',
$util->convertSystemDateTime('2021-05-20 10:00')
);
}
public function testConvertSystemDateTime2(): void
{
$util = new DateTime('DD-MM-YYYY', 'HH:mm', 'Europe/Kyiv');
$this->assertEquals(
'2021-05-20 10:00am',
$util->convertSystemDateTime('2021-05-20 10:00', 'UTC', 'YYYY-MM-DD hh:mma')
);
}
public function testConvertSystemDate1(): void
{
$util = new DateTime('DD-MM-YYYY', 'HH:mm', 'Europe/Kyiv');
$this->assertEquals(
'20-05-2021',
$util->convertSystemDate('2021-05-20')
);
}
}

View File

@@ -0,0 +1,79 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Event;
use Espo\Core\Utils\Event\Context;
use Espo\Core\Utils\Event\CrossInstanceEventDispatcher;
use Espo\Core\Utils\Event\OriginProvider;
use PHPUnit\Framework\TestCase;
class CrossInstanceEventDispatcherTest extends TestCase
{
public function testDispatching(): void
{
$originProvider = $this->getOriginProvider();
$transport = new TestTransport();
$dispatcher = new CrossInstanceEventDispatcher(
transport: $transport,
originProvider: $originProvider,
);
$value = false;
$callback1 = function (TestCiEvent1 $event, Context $context) use (&$value) {
if (!$context->isLocal && $event->value === 'hello') {
$value = true;
}
};
$callback2 = function () use (&$value) {
$value = false;
};
$dispatcher->subscribe(TestCiEvent1::class, $callback1);
$dispatcher->subscribe(TestCiEvent2::class, $callback2);
$transport->dispatchForTest(TestCiEvent1::class, (object) ['value' => 'hello']);
$this->assertTrue($value);
}
private function getOriginProvider(): OriginProvider
{
$originProvider = $this->createMock(OriginProvider::class);
$originProvider->method('get')
->willReturn('test-id');
return $originProvider;
}
}

View File

@@ -0,0 +1,95 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Event;
use Espo\Core\Utils\Event\Configuration;
use Espo\Core\Utils\Event\Context;
use Espo\Core\Utils\Event\CrossInstanceEventDispatcher;
use Espo\Core\Utils\Event\EventDispatcher;
use Espo\Core\Utils\Event\OriginProvider;
use PHPUnit\Framework\TestCase;
class EventDispatcherTest extends TestCase
{
public function testLocalDispatching() : void
{
$originProvider = $this->getOriginProvider();
$crossInstanceDispatcher = $this->createMock(CrossInstanceEventDispatcher::class);
$dispatcher = new EventDispatcher(
originProvider: $originProvider,
crossInstanceDispatcher: $crossInstanceDispatcher,
configuration: $this->createMock(Configuration::class),
);
//
$value = false;
$callback1 = function (TestEvent1 $event, Context $context) use (&$value, $originProvider) {
if ($context->isLocal && $context->origin === $originProvider->get()) {
$value = true;
}
};
$dispatcher->subscribe(TestEvent1::class, $callback1);
$dispatcher->subscribe(
TestEvent2::class,
function () use (&$value) {
$value = false;
}
);
$dispatcher->dispatch(new TestEvent1());
$this->assertTrue($value);
//
$dispatcher->unsubscribe(TestEvent1::class, $callback1);
$value = false;
$dispatcher->dispatch(new TestEvent1());
$this->assertFalse($value);
}
private function getOriginProvider(): OriginProvider
{
$originProvider = $this->createMock(OriginProvider::class);
$originProvider->method('get')
->willReturn('test-id');
return $originProvider;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Event;
use Espo\Core\Utils\Event\CrossInstanceEvent;
use stdClass;
class TestCiEvent1 implements CrossInstanceEvent
{
public function __construct(
public string $value,
) {}
public static function fromPayload(stdClass $payload): self
{
return new self($payload->value ?? '');
}
public function toPayload(): stdClass
{
return (object) [
'value' => $this->value,
];
}
}

View File

@@ -0,0 +1,46 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Event;
use Espo\Core\Utils\Event\CrossInstanceEvent;
use stdClass;
class TestCiEvent2 implements CrossInstanceEvent
{
public static function fromPayload(stdClass $payload): self
{
return new self();
}
public function toPayload(): stdClass
{
return (object) [];
}
}

View File

@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Event;
use Espo\Core\Utils\Event\Event;
class TestEvent1 implements Event
{}

View File

@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Event;
use Espo\Core\Utils\Event\Event;
class TestEvent2 implements Event
{}

View File

@@ -0,0 +1,72 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Event;
use Closure;
use Espo\Core\Utils\Event\Envelope;
use Espo\Core\Utils\Event\EventTransport;
use stdClass;
class TestTransport implements EventTransport
{
/**
* @var (Closure(Envelope): void)|null
*/
private ?Closure $callback = null;
/**
* @param Closure(Envelope): void $callback
*/
public function subscribe(Closure $callback): void
{
$this->callback = $callback;
}
public function publish(Envelope $envelope): void
{}
public function tick(): void
{}
public function dispatchForTest(string $eventClassName, stdClass $payload): void
{
if (!$this->callback) {
return;
}
$envelope = new Envelope(
eventClassName: $eventClassName,
payload: $payload,
origin: 'other',
);
($this->callback)($envelope);
}
}

View File

@@ -0,0 +1,132 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\FieldUtil;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Util;
use PHPUnit\Framework\TestCase;
class FieldUtilTest extends TestCase
{
private ?Metadata $metadata = null;
protected function setUp(): void
{
$this->metadata = $this->createMock(Metadata::class);
}
/**
* @param array<string, mixed> $data
*/
private function initMetadata(array $data): void
{
$this->metadata
->expects($this->any())
->method('get')
->willReturnCallback(function ($key, $default) use ($data) {
return Util::getValueByKey($data, $key, $default);
});
}
public function testGetActualAttributeListSuffix(): void
{
$this->initMetadata([
'fields' => [
'testType' => [
'naming' => 'suffix',
'actualFields' => [
'',
'helloOne',
],
],
],
'entityDefs' => [
'Test' => [
'fields' => [
'test' => [
'type' => 'testType',
'additionalAttributeList' => ['helloTwo'],
'fullNameAdditionalAttributeList' => ['helloThree'],
]
]
]
]
]);
$fieldUtil = new FieldUtil($this->metadata);
$actual = $fieldUtil->getActualAttributeList('Test', 'test');
$this->assertEquals([
'test',
'testHelloOne',
'testHelloTwo',
'helloThree',
], $actual);
}
public function testGetActualAttributeListPrefix(): void
{
$this->initMetadata([
'fields' => [
'testType' => [
'naming' => 'prefix',
'actualFields' => [
'',
'helloOne',
],
],
],
'entityDefs' => [
'Test' => [
'fields' => [
'test' => [
'type' => 'testType',
'additionalAttributeList' => ['helloTwo'],
'fullNameAdditionalAttributeList' => ['helloThree'],
]
]
]
]
]);
$fieldUtil = new FieldUtil($this->metadata);
$actual = $fieldUtil->getActualAttributeList('Test', 'test');
$this->assertEquals([
'test',
'helloOneTest',
'helloTwoTest',
'helloThree',
], $actual);
}
}

View File

@@ -0,0 +1,219 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\File;
use PHPUnit\Framework\TestCase;
use tests\unit\ReflectionHelper;
use Espo\Core\Utils\File\ClassMap;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Module;
use Espo\Core\Utils\Module\PathProvider;
class ClassMapTest extends TestCase
{
/** @var ClassMap */
private $classMap;
private $dataCache;
protected $reflection;
private $systemConfig;
private $customPath = 'tests/unit/testData/EntryPoints/Espo/Custom/';
private $corePath = 'tests/unit/testData/EntryPoints/Espo/';
private $modulePath = 'tests/unit/testData/EntryPoints/Espo/Modules/{*}/';
/** @var Module */
private $module;
protected function setUp(): void
{
$fileManager = new FileManager();
$this->systemConfig = $this->createMock(Config\SystemConfig::class);
$this->module = $this->createMock(Module::class);
$this->dataCache = $this->createMock(DataCache::class);
$pathProvider = $this->createMock(PathProvider::class);
$pathProvider
->method('getCustom')
->willReturn($this->customPath);
$pathProvider
->method('getCore')
->willReturn($this->corePath);
$pathProvider
->method('getModule')
->willReturnCallback(
function (?string $moduleName): string {
if ($moduleName === null) {
return $this->modulePath;
}
return str_replace('{*}', $moduleName, $this->modulePath);
}
);
$this->module
->method('getOrderedList')
->willReturn(['Crm']);
$this->classMap = new ClassMap(
$fileManager,
$this->module,
$this->dataCache,
$pathProvider,
$this->systemConfig,
);
$this->reflection = new ReflectionHelper($this->classMap);
}
public function testGetDataWithNoCache1(): void
{
$expected = [
'Download' => 'tests\unit\testData\EntryPoints\Espo\EntryPoints\Download',
'TestEntry' => 'tests\unit\testData\EntryPoints\Espo\EntryPoints\TestEntry',
'InModule' => 'tests\unit\testData\EntryPoints\Espo\Modules\Crm\EntryPoints\InModule'
];
$this->assertEquals($expected, $this->classMap->getData('EntryPoints', null, ['run']));
}
public function testGetDataWithNoCache2(): void
{
$this->dataCache
->expects($this->once())
->method('has')
->with('entryPoints')
->willReturn(true);
$this->systemConfig
->expects($this->exactly(2))
->method('useCache')
->willReturn(false);
$this->module
->expects($this->once())
->method('getOrderedList')
->willReturn(
['Crm']
);
$cacheKey = 'entryPoints';
$result = [
'Download' => 'tests\unit\testData\EntryPoints\Espo\EntryPoints\Download',
'TestEntry' => 'tests\unit\testData\EntryPoints\Espo\EntryPoints\TestEntry',
'InModule' => 'tests\unit\testData\EntryPoints\Espo\Modules\Crm\EntryPoints\InModule',
];
$this->assertEquals(
$result,
$this->classMap->getData('EntryPoints', $cacheKey, ['run'])
);
}
public function testGetDataWithCache(): void
{
$result = [
'Download' => 'tests\\unit\\testData\\EntryPoints\\Espo\\EntryPoints\\Download',
];
$this->systemConfig
->expects($this->once())
->method('useCache')
->willReturn(true);
$cacheKey = 'entryPoints';
$this->dataCache
->expects($this->once())
->method('has')
->with('entryPoints')
->willReturn(true);
$this->dataCache
->expects($this->once())
->method('get')
->with('entryPoints')
->willReturn($result);
$this->module
->expects($this->never())
->method('getOrderedList');
$this->assertEquals($result, $this->classMap->getData('EntryPoints', $cacheKey, ['run']));
}
public function testGetDataWithNoCacheString(): void
{
$this->systemConfig
->expects($this->exactly(2))
->method('useCache')
->willReturn(true);
$this->module
->expects($this->once())
->method('getOrderedList')
->willReturn(
['Crm']
);
$cacheKey = 'entryPoints';
$result = [
'Download' => 'tests\unit\testData\EntryPoints\Espo\EntryPoints\Download',
'TestEntry' => 'tests\unit\testData\EntryPoints\Espo\EntryPoints\TestEntry',
'InModule' => 'tests\unit\testData\EntryPoints\Espo\Modules\Crm\EntryPoints\InModule',
];
$this->dataCache
->expects($this->once())
->method('has')
->with($cacheKey)
->willReturn(true);
$this->dataCache
->expects($this->once())
->method('get')
->with($cacheKey)
->willReturn(null);
$this->assertEquals(
$result,
$this->classMap->getData('EntryPoints', $cacheKey, ['run'])
);
}
}

View File

@@ -0,0 +1,558 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\File;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use tests\unit\ReflectionHelper;
use Espo\Core\Utils\Util;
use Espo\Core\Utils\File\Manager as FileManager;
class ManagerTest extends TestCase
{
/**
* @var FileManager
*/
private $fileManager;
protected $filesPath = 'tests/unit/testData/FileManager';
protected const CACHE_PATH = 'tests/unit/testData/cache/FileManager';
protected $reflection;
protected function setUp(): void
{
$this->fileManager = new FileManager();
$this->reflection = new ReflectionHelper($this->fileManager);
}
public function testGetFileName()
{
$this->assertEquals('Donwload', $this->fileManager->getFileName('Donwload.php'));
$this->assertEquals('Donwload', $this->fileManager->getFileName('/Donwload.php'));
$this->assertEquals('Donwload', $this->fileManager->getFileName('\Donwload.php'));
$this->assertEquals('Donwload', $this->fileManager->getFileName('application/Espo/EntryPoints/Donwload.php'));
}
public function testGetContents()
{
$result = file_get_contents($this->filesPath . '/getContent/test.json');
$this->assertEquals(
$result,
$this->fileManager->getContents($this->filesPath . '/getContent/test.json')
);
}
public function testPutContents()
{
$testPath = self::CACHE_PATH;
$result= 'next value';
$this->assertTrue(
$this->fileManager->putContents($testPath . '/setContent.json', $result)
);
$this->assertEquals(
$result,
$this->fileManager->getContents($testPath . '/setContent.json')
);
@unlink($testPath . '/setContent.json');
}
public function testGetDirName()
{
$input = 'data/logs/espo.log';
$result = 'logs';
$this->assertEquals($result, $this->fileManager->getDirName($input, false));
$input = 'data/logs/espo.log/';
$result = 'logs';
$this->assertEquals($result, $this->fileManager->getDirName($input, false));
$input = 'application/Espo/Resources/metadata/entityDefs';
$result = 'entityDefs';
$this->assertEquals($result, $this->fileManager->getDirName($input, false));
$input = 'application/Espo/Resources/metadata/entityDefs/';
$result = 'entityDefs';
$this->assertEquals($result, $this->fileManager->getDirName($input, false));
//path doesn't exists. Be careful to use "/" at the beginning
$input = '/application/Espo/Resources/metadata/entityDefs';
$result = 'metadata';
$this->assertEquals($result, $this->fileManager->getDirName($input, false));
$input = 'notRealPath/logs/espo.log';
$result = 'logs';
$this->assertEquals($result, $this->fileManager->getDirName($input, false));
$input = $this->filesPath . '/getContent';
$result = 'getContent';
$this->assertEquals($result, $this->fileManager->getDirName($input, false));
}
public function testGetDirNameFullPath()
{
$input = 'data/logs/espo.log';
$result = 'data/logs';
$this->assertEquals($result, $this->fileManager->getDirName($input));
$input = 'data/logs/espo.log/';
$result = 'data/logs';
$this->assertEquals($result, $this->fileManager->getDirName($input));
$input = 'application/Espo/Resources/metadata/entityDefs';
$result = 'application/Espo/Resources/metadata/entityDefs';
$this->assertEquals($result, $this->fileManager->getDirName($input));
$input = 'application/Espo/Resources/metadata/entityDefs/';
$result = 'application/Espo/Resources/metadata/entityDefs';
$this->assertEquals($result, $this->fileManager->getDirName($input));
//path doesn't exists. Be careful to use "/" at the beginning
$input = '/application/Espo/Resources/metadata/entityDefs';
$result = '/application/Espo/Resources/metadata';
$this->assertEquals($result, $this->fileManager->getDirName($input));
$input = 'notRealPath/logs/espo.log';
$result = 'notRealPath/logs';
$this->assertEquals($result, $this->fileManager->getDirName($input));
$input = $this->filesPath . '/getContent';
$result = $this->filesPath . '/getContent';
$this->assertEquals($result, $this->fileManager->getDirName($input, true));
}
public function testUnsetContents()
{
$testPath = self::CACHE_PATH.'/unsets.json';
$initData = '{"fields":{"someName":{"type":"varchar","maxLength":40},"someName2":{"type":"varchar","maxLength":36}}}';
$this->fileManager->putContents($testPath, $initData);
$unsets = ['fields.someName2'];
$this->assertTrue($this->fileManager->unsetJsonContents($testPath, $unsets));
$result = '{"fields":{"someName":{"type":"varchar","maxLength":40}}}';
$this->assertJsonStringEqualsJsonFile($testPath, $result);
}
public function testIsDirEmpty()
{
$this->assertFalse($this->fileManager->isDirEmpty('application'));
$this->assertFalse($this->fileManager->isDirEmpty('tests/unit/Espo'));
$this->assertFalse($this->fileManager->isDirEmpty('tests/unit/Espo/Core/Utils/File'));
$dirPath = 'tests/unit/testData/cache/EmptyDir';
if (file_exists($dirPath) || mkdir($dirPath, 0755)) {
$this->assertTrue($this->fileManager->isDirEmpty($dirPath));
}
}
public function testGetParentDirName()
{
$input = 'application/Espo/Resources/metadata/entityDefs';
$result = 'metadata';
$this->assertEquals($result, $this->fileManager->getParentDirName($input, false));
$input = 'application/Espo/Resources/metadata/entityDefs/';
$result = 'metadata';
$this->assertEquals($result, $this->fileManager->getParentDirName($input, false));
//path doesn't exists. Be careful to use "/" at the beginning
$input = '/application/Espo/Resources/metadata/entityDefs';
$result = 'metadata';
$this->assertEquals($result, $this->fileManager->getParentDirName($input, false));
//path doesn't exists. Be careful to use "/" at the beginning
$input = '/application/Espo/Resources/metadata/entityDefs';
$result = '/application/Espo/Resources/metadata';
$this->assertEquals($result, $this->fileManager->getParentDirName($input));
$input = 'notRealPath/logs/espo.log';
$result = 'notRealPath/logs';
$this->assertEquals($result, $this->fileManager->getParentDirName($input));
$input = $this->filesPath . '/getContent';
$result = $this->filesPath;
$this->assertEquals($result, $this->fileManager->getParentDirName($input, true));
}
public function testGetSingleFileListAll()
{
$input = array (
'custom' =>
array (
'Espo' =>
array (
'Custom' =>
array (
'Modules' =>
array (
'ExtensionTest' =>
array (
0 => 'File.json',
1 => 'File.php',
),
),
),
),
),
);
$result = array (
'custom',
'custom/Espo',
'custom/Espo/Custom',
'custom/Espo/Custom/Modules',
'custom/Espo/Custom/Modules/ExtensionTest',
'custom/Espo/Custom/Modules/ExtensionTest/File.json',
'custom/Espo/Custom/Modules/ExtensionTest/File.php',
);
$result = array_map('\Espo\Core\Utils\Util::fixPath', $result);
$this->assertEquals($result, $this->reflection->invokeMethod('getSingleFileList', array($input)));
}
public function testGetSingeFileListOnlyFiles()
{
$input = array (
'custom' =>
array (
'Espo' =>
array (
'Custom' =>
array (
'Modules' =>
array (
'ExtensionTest' =>
array (
0 => 'File.json',
1 => 'File.php',
),
),
),
),
),
);
$result = array (
'custom/Espo/Custom/Modules/ExtensionTest/File.json',
'custom/Espo/Custom/Modules/ExtensionTest/File.php',
);
$result = array_map('\Espo\Core\Utils\Util::fixPath', $result);
$this->assertEquals($result, $this->reflection->invokeMethod('getSingleFileList', array($input, true)));
}
public function testGetSingeFileListOnlyDirs()
{
$input = array (
'custom' =>
array (
'Espo' =>
array (
'Custom' =>
array (
'Modules' =>
array (
'ExtensionTest' =>
array (
0 => 'File.json',
1 => 'File.php',
),
),
),
),
),
);
$result = array (
'custom',
'custom/Espo',
'custom/Espo/Custom',
'custom/Espo/Custom/Modules',
'custom/Espo/Custom/Modules/ExtensionTest',
);
$result = array_map('\Espo\Core\Utils\Util::fixPath', $result);
$this->assertEquals($result, $this->reflection->invokeMethod('getSingleFileList', array($input, false)));
}
static public function fileListSets()
{
return [
['Set1', [
'custom',
'custom/Espo',
'custom/Espo/Custom',
'custom/Espo/Custom/Modules',
'custom/Espo/Custom/Modules/TestModule',
'custom/Espo/Custom/Modules/TestModule/SubFolder',
'custom/Espo/Custom/Modules/TestModule/SubFolder/Tester.txt',
]
],
['Set2', [
'custom',
'custom/Espo',
'custom/Espo/Custom',
'custom/Espo/Custom/Resources',
'custom/Espo/Custom/Resources/metadata',
'custom/Espo/Custom/Resources/metadata/entityDefs',
'custom/Espo/Custom/Resources/metadata/entityDefs/Account.json',
]
],
['Set3', [
'custom',
'custom/test.file',
]
],
];
}
#[DataProvider('fileListSets')]
public function testRemoveWithEmptyDirs($name, $result)
{
$path = Util::fixPath($this->filesPath . '/Remove/' . $name);
$cachePath = Util::fixPath(self::CACHE_PATH . '/' . $name);
$result = array_map('\Espo\Core\Utils\Util::fixPath', $result);
$fileList = array (
$cachePath . '/custom/Espo/Custom/Modules/ExtensionTest/File.json',
$cachePath . '/custom/Espo/Custom/Modules/ExtensionTest/File.php',
);
$fileList = array_map('\Espo\Core\Utils\Util::fixPath', $fileList);
$res = $this->fileManager->copy($path, $cachePath, true);
if ($res) {
$this->assertTrue($this->fileManager->remove($fileList, null, true));
$this->assertEquals($result, $this->fileManager->getFileList($cachePath, true, '', null, true));
}
}
static public function existsPathSet()
{
return [
['application/Espo/Core/Application.php', 'application/Espo/Core/Application.php',],
['application/Espo/Core/NotRealApplication.php', 'application/Espo/Core'],
['application/Espo/Core/NotRealApplication.php', 'application/Espo/Core'],
['application/NoEspo/Core/Application.php', 'application'],
['notRealPath/Espo/Core/Application.php', '.'],
];
}
#[DataProvider('existsPathSet')]
public function testGetExistsPath($input, $result)
{
$this->assertEquals(
$result,
$this->reflection->invokeMethod('getExistsPath', [$input])
);
}
public function testCopyTestCase1()
{
$path = Util::fixPath($this->filesPath . '/copy/testCase1');
$cachePath = Util::fixPath(self::CACHE_PATH . '/copy/testCase1');
$expectedResult = [
'custom/Espo/Custom/Modules/ExtensionTest/File.json',
'custom/Espo/Custom/Modules/ExtensionTest/File.php',
'custom/Espo/Custom/Modules/TestModule/SubFolder/Tester.txt',
];
$expectedResult = array_map('\Espo\Core\Utils\Util::fixPath', $expectedResult);
$result = $this->fileManager->copy($path, $cachePath, true);
if ($result) {
$this->assertEquals($expectedResult, $this->fileManager->getFileList($cachePath, true, '', true, true));
$this->fileManager->removeInDir($cachePath);
}
}
public function testCopyTestCase2()
{
$path = Util::fixPath($this->filesPath . '/copy/testCase2');
$cachePath = Util::fixPath(self::CACHE_PATH . '/copy/testCase2');
$expectedResult = [
'custom/Espo/Custom/test1.php',
'data/test2.php',
'data/upload/5a86d9bf1154968dc',
'test0.php'
];
$expectedResult = array_map('\Espo\Core\Utils\Util::fixPath', $expectedResult);
$result = $this->fileManager->copy($path, $cachePath, true);
if ($result) {
$this->assertEquals($expectedResult, $this->fileManager->getFileList($cachePath, true, '', true, true));
$this->fileManager->removeInDir($cachePath);
}
}
public function testCopyTestCase3()
{
$path = Util::fixPath($this->filesPath . '/copy/testCase3');
$cachePath = Util::fixPath(self::CACHE_PATH . '/copy/testCase3');
$expectedResult = [
'custom/Espo/Custom/test1.php',
'data/test2.php',
'data/upload/5a86d9bf1154968dc',
'test0.php'
];
$expectedResult = array_map('\Espo\Core\Utils\Util::fixPath', $expectedResult);
$fileList = $this->fileManager->getFileList($path, true, '', true, true);
$this->assertEquals($expectedResult, $fileList, "Expected Result and File List");
$result = $this->fileManager->copy($path, $cachePath, true, $fileList);
if ($result) {
$this->assertEquals(
$expectedResult,
$this->fileManager->getFileList($cachePath, true, '', true, true),
"Expected Result and List of copied files"
);
$this->fileManager->removeInDir($cachePath);
}
}
public function testCopyTestCase4()
{
$path = Util::fixPath($this->filesPath . '/copy/testCase4');
$cachePath = Util::fixPath(self::CACHE_PATH . '/copy/testCase4');
$expectedResult = [
'custom',
'custom/Espo',
'custom/Espo/Custom',
'custom/Espo/Custom/test1.php',
'data',
'data/test2.php',
'data/upload',
'data/upload/5a86d9bf1154968dc',
'test0.php'
];
$expectedResult = array_map('\Espo\Core\Utils\Util::fixPath', $expectedResult);
$fileList = $this->fileManager->getFileList($path, true, '', null, true);
$this->assertEquals($expectedResult, $fileList, "Expected Result and File List");
$result = $this->fileManager->copy($path, $cachePath, true, $fileList);
if ($result) {
$this->assertEquals(
$expectedResult,
$this->fileManager->getFileList($cachePath, true, '', null, true),
"Expected Result and List of copied files"
);
$this->fileManager->removeInDir($cachePath);
}
}
static public function relativePathData()
{
$tmpPath = self::CACHE_PATH;
if (!file_exists($tmpPath)) {
mkdir($tmpPath, 0775, true);
}
$tmpFile = tempnam($tmpPath, 'tmp');
$data = [
['data/config.php', 'data/config.php'],
[realpath('vendor/autoload.php'), 'vendor/autoload.php'],
[$tmpFile, $tmpPath . '/' . basename($tmpFile)],
[realpath('application/Espo/Core'), 'application/Espo/Core'],
[realpath('application/Espo/Core') . '/', 'application/Espo/Core/'],
[realpath('application/Espo/Core/Application.php'), 'application/Espo/Core/Application.php'],
['C:\\espocrm\\data\\config.php', 'data\\config.php', 'C:\\espocrm', '\\'],
['C:espocrm\\data\\config.php', 'data\\config.php', 'C:espocrm', '\\'],
['C:\\espocrm\\data\\tmp\\' . basename($tmpFile), 'data\\tmp\\' . basename($tmpFile), 'C:\\espocrm', '\\'],
];
@unlink($tmpFile);
return $data;
}
#[DataProvider('relativePathData')]
public function testGetRelativePath($path, $expectedResult, $basePath = null, $dirSeparator = null)
{
$this->assertEquals(
Util::fixPath($expectedResult),
$this->fileManager->getRelativePath($path, $basePath, $dirSeparator)
);
}
public static function reservedCustomPathData(): array
{
return [
['custom/Espo/Custom', true],
['custom/Espo/Modules', true],
['client/custom/modules', true],
['client/custom/modules/', true],
['custom/Espo/Custom/Resources', false],
['custom/Espo/Modules/Test', false],
['client/custom/modules/test', false],
['application/Espo/Core', false],
];
}
#[DataProvider('reservedCustomPathData')]
public function testIsReservedCustomPath(string $path, bool $expectedResult): void
{
$this->assertEquals(
$expectedResult,
$this->reflection->invokeMethod('isReservedCustomPath', [$path])
);
}
}

View File

@@ -0,0 +1,65 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\File;
use Espo\Core\Utils\File\MimeType;
use Espo\Core\Utils\Metadata;
class MimeTypeTest extends \PHPUnit\Framework\TestCase
{
private Metadata $metadata;
protected function setUp(): void
{
$this->metadata = $this->createMock(Metadata::class);
}
public function testGetMimeTypeByExtension(): void
{
$this->metadata
->expects($this->any())
->method('get')
->with(['app', 'file', 'extensionMimeTypeMap', 'csv'])
->willReturn(['text/csv']);
$util = new MimeType($this->metadata);
$this->assertEquals('text/csv', $util->getMimeTypeByExtension('csv'));
$this->assertEquals('text/csv', $util->getMimeTypeByExtension('CSV'));
}
public function testMatchMimeTypeToAcceptToken(): void
{
$this->assertTrue(MimeType::matchMimeTypeToAcceptToken('text/csv', 'text/csv'));
$this->assertFalse(MimeType::matchMimeTypeToAcceptToken('text/csv', 'text/plain'));
$this->assertTrue(MimeType::matchMimeTypeToAcceptToken('video/mpeg', 'video/*'));
$this->assertFalse(MimeType::matchMimeTypeToAcceptToken('video/mpeg', 'image/*'));
}
}

View File

@@ -0,0 +1,127 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\File;
use Espo\Core\Utils\File\Permission;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use tests\unit\ReflectionHelper;
class PermissionTest extends TestCase
{
protected $object;
protected $objects;
protected $reflection;
protected $fileList;
protected function setUp() : void
{
$this->objects['fileManager'] = $this->getMockBuilder('\Espo\Core\Utils\File\Manager')->disableOriginalConstructor()->getMock();
$this->object = new Permission($this->objects['fileManager']);
$this->reflection = new ReflectionHelper($this->object);
$this->fileList = [
'application/Espo/Controllers/Email.php',
'application/Espo/Controllers/EmailAccount.php',
'application/Espo/Controllers/EmailAddress.php',
'application/Espo/Controllers/ExternalAccount.php',
'application/Espo/Controllers/Import.php',
'application/Espo/Controllers/Integration.php',
'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Calendar.json',
'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Call.json',
'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Case.json',
'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Contact.json',
'application/Espo/Modules/Crm/Resources/i18n/pl_PL/Global.json',
'application/Espo/Resources/layouts/User/filters.json',
'application/Espo/Resources/metadata/app/acl.json',
'application/Espo/Resources/metadata/app/defaultDashboardLayout.json'
];
}
protected function tearDown() : void
{
$this->object = NULL;
}
public function testGetSearchCount()
{
$search = 'application/Espo/Controllers/';
$methodResult = $this->reflection->invokeMethod('getSearchCount', array($search, $this->fileList));
$result = 6;
$this->assertEquals($result, $methodResult);
$search = 'application/Espo/Controllers/Email.php';
$methodResult = $this->reflection->invokeMethod('getSearchCount', array($search, $this->fileList));
$result = 1;
$this->assertEquals($result, $methodResult);
$search = 'application/Espo/Controllers/NotReal';
$methodResult = $this->reflection->invokeMethod('getSearchCount', array($search, $this->fileList));
$result = 0;
$this->assertEquals($result, $methodResult);
}
public function testArrangePermissionList()
{
$result = array(
'application/Espo/Controllers',
'application/Espo/Modules/Crm/Resources/i18n/pl_PL',
'application/Espo/Resources/layouts/User/filters.json',
'application/Espo/Resources/metadata/app',
);
$this->assertEquals( $result, $this->object->arrangePermissionList($this->fileList) );
}
static public function requiredPermissionsData()
{
return [
['data/config.php', '0775', '0664'],
['data/tmp/tmpivqW1X', '0775', '0664'],
['application/Espo/Core/Application.php', '0755', '0644'],
['custom/Espo/Custom/Resources/metadata/entityDefs', '0775', '0664'],
['custom/Espo/Custom/Resources/metadata/entityDefs/Account.json', '0775', '0664'],
['custom/Espo/Modules', '0775', '0664'],
['client/modules/crm/src', '0755', '0644'],
['client/modules/crm/src/views/account/detail.js', '0755', '0644'],
];
}
#[DataProvider('requiredPermissionsData')]
public function testGetRequiredPermissions($path, $dirPermission, $filePermission)
{
$result = $this->object->getRequiredPermissions($path);
$this->assertEquals($dirPermission, $result['dir']);
$this->assertEquals($filePermission, $result['file']);
}
}

View File

@@ -0,0 +1,72 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\Json;
use JsonException;
class JsonTest extends \PHPUnit\Framework\TestCase
{
protected function setUp(): void
{
}
protected function tearDown() : void
{
}
public function testDecodeBad1()
{
$value = '{';
$this->expectException(JsonException::class);
Json::decode($value);
}
public function testEncode()
{
$testVal = ['testOption' => 'Test'];
$this->assertEquals(json_encode($testVal), Json::encode($testVal));
}
public function testDecode()
{
$value = ['testOption' => 'Test'];
$this->assertEquals($value, Json::decode(json_encode($value), true));
$test = '{"folder":"data\/logs"}';
$this->assertEquals('data/logs', Json::decode($test)->folder);
}
}

View File

@@ -0,0 +1,370 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use PHPUnit\Framework\TestCase;
use tests\unit\ReflectionHelper;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Language;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\File\Unifier;
use Espo\Core\Utils\File\UnifierObj;
use Espo\Core\Utils\Module;
use Espo\Core\Utils\Resource\Reader;
use Espo\Core\Utils\Resource\PathProvider;
class LanguageTest extends TestCase
{
private $object;
private $reflection;
protected $corePath = 'tests/unit/testData/Utils/I18n/Espo/Resources/';
protected $modulePath = 'tests/unit/testData/Utils/I18n/Espo/Modules/{*}/Resources/';
protected $customPath = 'tests/unit/testData/Utils/I18n/Espo/Custom/Resources/';
protected function setUp(): void
{
$fileManager = new FileManager();
$dataCache = $this->createMock(DataCache::class);
$metadata = $this->createMock(Metadata::class);
$metadata->expects($this->any())
->method('getModuleList')
->willReturn(
[
'Crm',
]
);
$module = new Module($fileManager);
$pathProvider = $this->createMock(PathProvider::class);
$pathProvider
->method('getCustom')
->willReturn($this->customPath);
$pathProvider
->method('getCore')
->willReturn($this->corePath);
$pathProvider
->method('getModule')
->willReturnCallback(
function (?string $moduleName): string {
if ($moduleName === null) {
return $this->modulePath;
}
return str_replace('{*}', $moduleName, $this->modulePath);
}
);
$unifierObj = new UnifierObj($fileManager, $module, $pathProvider);
$unifier = new Unifier($fileManager, $module, $pathProvider);
$reader = new Reader($unifier, $unifierObj);
$this->object = new Language(
null,
$fileManager,
$reader,
$dataCache,
false
);
$this->reflection = new ReflectionHelper($this->object);
$this->customPath = 'tests/unit/testData/Utils/I18n/Espo/Custom/Resources/i18n/{language}';
$this->reflection->setProperty('customPath', $this->customPath);
$this->reflection->setProperty('currentLanguage', 'en_US');
}
protected function tearDown(): void
{
$this->object = NULL;
}
public function testLanguage()
{
$this->assertEquals('en_US', $this->object->getLanguage());
$originalLang = $this->object->getLanguage();
$this->object->setLanguage('lang_TEST');
$this->assertEquals('lang_TEST', $this->object->getLanguage());
$this->object->setLanguage($originalLang);
}
public function testGetData()
{
$result = [
'User' => [
'fields' => [
'name' => 'User',
'label' => 'Core',
'source' => 'Core',
],
],
'Account' => [
'fields' => [
'name' => 'Account',
'label' => 'Custom',
'source' => 'Crm Module',
],
],
'Contact' => [
'fields' => [
'name' => 'Contact',
'label' => 'Custom',
'source' => 'Crm Module',
],
],
'Global' => [
'options' => [
'language' => [
'en_US' => 'English (United States)',
]
],
],
];
$this->assertEquals($result, $this->reflection->invokeMethod('getData', []));
}
public function testGet()
{
$result = array (
'fields' =>
array(
'name' => 'User',
'label' => 'Core',
'source' => 'Core',
),
);
$this->assertEquals($result, $this->object->get('User'));
$result = 'User';
$this->assertEquals($result, $this->object->get('User.fields.name'));
}
public function testTranslate()
{
$this->assertEquals('Core', $this->object->translate('label', 'fields', 'User'));
$input = array(
'name',
'label',
);
$result = array(
'name' => 'User',
'label' => 'Core',
);
$this->assertEquals($result, $this->object->translate($input, 'fields', 'User'));
}
public function testTranslateTestGlobal()
{
$result = array(
'en_US' => 'English (United States)',
);
$this->assertEquals($result, $this->object->translate('language', 'options', 'User'));
}
public function testTranslateOption()
{
$result = array(
'en_US' => 'English (United States)',
);
$this->assertEquals($result, $this->object->translate('language', 'options'));
}
public function testTranslateOptionWithRequiredOptions()
{
$result = array(
'en_US' => 'English (United States)',
'de_DE' => 'de_DE',
);
$requiredOptions = array(
'en_US',
'de_DE',
);
$this->assertEquals($result, $this->object->translate('language', 'options', 'Global', $requiredOptions));
}
public function testTranslateArray()
{
$input = array(
'name',
'label',
);
$result = array(
'name' => 'User',
'label' => 'Core',
);
$this->assertEquals($result, $this->object->translate($input, 'fields', 'User'));
}
public function testTranslateSubLabels()
{
$result = 'English (United States)';
$this->assertEquals($result, $this->object->translate('language.en_US', 'options'));
}
public function testSet()
{
$label = 'TEST';
$this->object->set('User', 'fields', 'label', $label);
$this->assertEquals($label, $this->object->translate('label', 'fields', 'User'));
$result = array(
'User' => array(
'fields' => array(
'label' => 'TEST',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
$label2 = 'TEST2';
$this->object->set('User', 'fields', 'name', $label2);
$this->assertEquals($label2, $this->object->translate('name', 'fields', 'User'));
$result = array(
'User' => array(
'fields' => array(
'label' => 'TEST',
'name' => 'TEST2',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
$label3 = 'TEST3';
$this->object->set('Account', 'fields', 'name', $label3);
$this->assertEquals($label3, $this->object->translate('name', 'fields', 'Account'));
$result = array(
'User' => array(
'fields' => array(
'label' => 'TEST',
'name' => 'TEST2',
),
),
'Account' => array(
'fields' => array(
'name' => 'TEST3',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
$this->object->clearChanges();
$this->assertEquals(array(), $this->reflection->getProperty('changedData'));
$this->assertNotEquals('TEST', $this->object->get('User', 'fields', 'label'));
}
public function testDelete()
{
$this->object->delete('User', 'fields', 'label');
$this->assertNull($this->object->get('User.fields.label'));
$result = array(
'User' => array(
'fields' => array(
'label',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
$this->object->delete('User', 'fields', 'name');
$this->assertNull($this->object->get('User.fields.name'));
$result = array(
'User' => array(
'fields' => array(
'label',
'name',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
$this->object->clearChanges();
$this->assertNotNull($this->object->get('User.fields.label'));
$this->assertNotNull($this->object->get('User.fields.name'));
$this->assertEquals(array(), $this->reflection->getProperty('deletedData'));
}
public function testUndelete()
{
$this->object->delete('User', 'fields', 'label');
$this->assertNull($this->object->get('User.fields.label'));
$this->object->delete('User', 'fields', 'name');
$this->assertNull($this->object->get('User.fields.name'));
$label = 'TEST';
$this->object->set('User', 'fields', 'label', $label);
$this->assertEquals($label, $this->object->translate('label', 'fields', 'User'));
$result = array(
'User' => array(
'fields' => array(
1 => 'name',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
$label2 = 'TEST2';
$this->object->set('User', 'fields', 'name', $label2);
$this->assertEquals($label2, $this->object->translate('name', 'fields', 'User'));
$result = array(
'User' => array(
'fields' => array(
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
}
}

View File

@@ -0,0 +1,130 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\Metadata;
use Espo\Tools\Layout\LayoutProvider;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Resource\FileReader;
use Espo\Core\Utils\Resource\FileReader\Params as FileReaderParams;
class LayoutTest extends \PHPUnit\Framework\TestCase
{
/** @var LayoutProvider */
private $layout;
/** @var InjectableFactory */
private $injectableFactory;
/** @var FileManager */
private $fileManager;
private $fileReader;
protected function setUp(): void
{
$this->fileManager = $this->createMock(FileManager::class);
$this->injectableFactory = $this->createMock(InjectableFactory::class);
$this->fileReader = $this->createMock(FileReader::class);
$metadata = $this->createMock(Metadata::class);
$this->layout = new LayoutProvider(
$this->fileManager,
$this->injectableFactory,
$metadata,
$this->fileReader
);
}
public function testGet1(): void
{
$this->fileReader
->expects($this->once())
->method('exists')
->with(
'layouts/Test/test.json',
$this->callback(
function (FileReaderParams $params): bool {
return $params->getScope() === 'Test';
}
)
)
->willReturn(true);
$this->fileReader
->expects($this->once())
->method('read')
->with(
'layouts/Test/test.json',
$this->callback(
function (FileReaderParams $params): bool {
return $params->getScope() === 'Test';
}
)
)
->willReturn('["test"]');
$result = $this->layout->get('Test', 'test');
$this->assertEquals('["test"]', $result);
}
public function testGetDefault(): void
{
$this->fileReader
->expects($this->once())
->method('exists')
->with(
'layouts/Test/test.json',
$this->callback(
function (FileReaderParams $params): bool {
return $params->getScope() === 'Test';
}
)
)
->willReturn(false);
$this->fileManager
->expects($this->once())
->method('isFile')
->with('application/Espo/Resources/defaults/layouts/test.json')
->willReturn(true);
$this->fileManager
->expects($this->once())
->method('getContents')
->with('application/Espo/Resources/defaults/layouts/test.json')
->willReturn('["test"]');
$result = $this->layout->get('Test', 'test');
$this->assertEquals('["test"]', $result);
}
}

View File

@@ -0,0 +1,353 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\Cache\DataCacheAccess;
use Espo\Core\Utils\Config\SystemConfig;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\Json;
use PHPUnit\Framework\TestCase;
use tests\unit\ReflectionHelper;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\Log;
use Espo\Core\Utils\File\UnifierObj;
use Espo\Core\Utils\File\Unifier;
use Espo\Core\Utils\Module;
use Espo\Core\Utils\Module\PathProvider as ModulePathProvider;
use Espo\Core\Utils\Resource\Reader;
use Espo\Core\Utils\Resource\PathProvider;
class MetadataTest extends TestCase
{
private ?Metadata $metadata = null;
private $reflection;
private ?FileManager $fileManager = null;
private $customPath;
protected function setUp(): void
{
$this->fileManager = new FileManager();
$module = new Module($this->fileManager);
$pathProvider = new PathProvider(new ModulePathProvider($module));
$unifierObj = new UnifierObj($this->fileManager, $module, $pathProvider);
$unifier = new Unifier($this->fileManager, $module, $pathProvider);
$reader = new Reader($unifier, $unifierObj);
$builderHelper = new Metadata\BuilderHelper();
$builder = new Metadata\Builder($reader);
$dataCacheAccess = new DataCacheAccess(
dataCache: $this->createMock(DataCache::class),
systemConfig: $this->createMock(SystemConfig::class),
log: $this->createMock(Log::class),
);
$objectDataCacheAccess = new DataCacheAccess(
dataCache: $this->createMock(DataCache::class),
systemConfig: $this->createMock(SystemConfig::class),
log: $this->createMock(Log::class),
);
$this->metadata = new Metadata(
fileManager: $this->fileManager,
module: $module,
builder: $builder,
builderHelper: $builderHelper,
data: $dataCacheAccess,
objectData: $objectDataCacheAccess,
);
$this->reflection = new ReflectionHelper($this->metadata);
$this->customPath = 'tests/unit/testData/cache/metadata/custom';
$this->reflection->setProperty('customPath', $this->customPath);
}
protected function tearDown() : void
{
$this->metadata->clearChanges();
$this->metadata = null;
}
public function testGet(): void
{
$this->assertEquals('System', $this->metadata->get('app.adminPanel.system.label'));
$this->assertArrayHasKey('fields', $this->metadata->get('entityDefs.User'));
}
public function testSet(): void
{
$data = [
'fields' =>
[
'name' =>
[
'required' => false,
'maxLength' => 150,
'view' => 'Views.Test.Custom',
],
],
];
$this->metadata->set('entityDefs', 'Attachment', $data);
$this->assertEquals('Views.Test.Custom', $this->metadata->get('entityDefs.Attachment.fields.name.view'));
$this->assertEquals(150, $this->metadata->get('entityDefs.Attachment.fields.name.maxLength'));
$result = [
'entityDefs' => [
'Attachment' => $data
],
];
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
$data = [
'fields' =>
[
'name' =>
[
'maxLength' => 200,
],
],
];
$this->metadata->set('entityDefs', 'Attachment', $data);
$this->assertEquals(200, $this->metadata->get('entityDefs.Attachment.fields.name.maxLength'));
$this->assertEquals('Views.Test.Custom', $this->metadata->get('entityDefs.Attachment.fields.name.view'));
$result = [
'entityDefs' => [
'Attachment' => [
'fields' =>
[
'name' =>
[
'required' => false,
'maxLength' => 200,
'view' => 'Views.Test.Custom',
],
],
],
],
];
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
$this->metadata->clearChanges();
$this->assertEquals([], $this->reflection->getProperty('changedData'));
$this->assertEquals(255, $this->metadata->get('entityDefs.Attachment.fields.name.maxLength'));
}
public function testDelete(): void
{
$this->metadata->delete('entityDefs', 'Attachment', [
'fields.name.type',
]);
$this->assertNull($this->metadata->get('entityDefs.Attachment.fields.name.type'));
$this->assertEquals([
'entityDefs' => [
'Attachment' => [
'fields.name.type',
],
],
], $this->reflection->getProperty('deletedData'));
$this->metadata->delete('entityDefs', 'Attachment', [
'fields.name.required',
]);
$this->assertNull($this->metadata->get('entityDefs.Attachment.fields.name.required'));
$this->assertEquals([
'entityDefs' => [
'Attachment' => [
'fields.name.type',
'fields.name.required',
],
],
], $this->reflection->getProperty('deletedData'));
$this->metadata->init();
$this->assertNotNull($this->metadata->get('entityDefs.Attachment.fields.name.type'));
$this->assertNotNull($this->metadata->get('entityDefs.Attachment.fields.name.required'));
$this->metadata->clearChanges();
$this->assertEquals([], $this->reflection->getProperty('deletedData'));
}
public function testUndelete(): void
{
$this->metadata->delete('entityDefs', 'Attachment', [
'fields.name.type',
'fields.name.required',
]);
$this->assertNull($this->metadata->get('entityDefs.Attachment.fields.name.type'));
$this->metadata->set('entityDefs', 'Attachment', [
'fields' => [
'name' => [
'type' => 'enum',
],
],
]);
$this->assertEquals('enum', $this->metadata->get('entityDefs.Attachment.fields.name.type'));
$this->assertEquals([
'entityDefs' => [
'Attachment' => [
1 => 'fields.name.required',
],
],
], $this->reflection->getProperty('deletedData'));
$this->metadata->set('entityDefs', 'Attachment', [
'fields' => [
'name' => [
'required' => true,
],
],
]);
$this->assertEquals(true, $this->metadata->get('entityDefs.Attachment.fields.name.required'));
$this->assertEquals([
'entityDefs' => [
'Attachment' => [],
],
], $this->reflection->getProperty('deletedData'));
}
public function testGetCustom(): void
{
$this->assertNull($this->metadata->getCustom('entityDefs', 'Lead'));
$customData = $this->metadata->getCustom('entityDefs', 'Lead', (object) []);
$this->assertTrue(is_object($customData));
$data = (object) [
'fields' => (object) [
'status' => (object) [
"type" => "enum",
"options" => ["__APPEND__", "Test1", "Test2"],
],
],
];
$this->metadata->saveCustom('entityDefs', 'Lead', $data);
$this->assertEquals($data, $this->metadata->getCustom('entityDefs', 'Lead'));
unlink($this->customPath . '/entityDefs/Lead.json');
}
public function testSaveCustom1(): void
{
$data = (object) [
'fields' => (object) [
'status' => (object) [
"type" => "enum",
"options" => ["__APPEND__", "Test1", "Test2"],
],
],
];
$this->metadata->saveCustom('entityDefs', 'Lead', $data);
$savedFile = $this->customPath . '/entityDefs/Lead.json';
$fileContent = $this->fileManager->getContents($savedFile);
$savedData = Json::decode($fileContent);
$this->assertEquals($data, $savedData);
unlink($savedFile);
}
public function testSaveCustom2(): void
{
$initData = (object) [
'fields' => (object) [
'status' => (object) [
"type" => "enum",
"options" => ["__APPEND__", "Test1", "Test2"],
],
],
];
$this->metadata->saveCustom('entityDefs', 'Lead', $initData);
$customData = $this->metadata->getCustom('entityDefs', 'Lead');
unset($customData->fields->status->type);
$customData->fields->status->options = ["__APPEND__", "Test1"];
$this->metadata->saveCustom('entityDefs', 'Lead', $customData);
$savedFile = $this->customPath . '/entityDefs/Lead.json';
$fileContent = $this->fileManager->getContents($savedFile);
$savedData = Json::decode($fileContent);
$expectedData = (object) [
'fields' => (object) [
'status' => (object) [
"options" => ["__APPEND__", "Test1"],
],
],
];
$this->assertEquals($expectedData, $savedData);
unlink($savedFile);
}
public function testGetObjects(): void
{
$this->assertEquals('System', $this->metadata->getObjects('app.adminPanel.system.label'));
$this->assertObjectHasProperty('fields', $this->metadata->getObjects('entityDefs.User'));
$this->assertObjectHasProperty('type', $this->metadata->getObjects('entityDefs.User.fields.name'));
}
}

View File

@@ -0,0 +1,99 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\Module;
use Espo\Core\Utils\File\Manager as FileManager;
use PHPUnit\Framework\TestCase;
class ModuleTest extends TestCase
{
/** @var Module */
private $module;
/** @var FileManager */
private $fileManager;
protected function setUp(): void
{
$this->fileManager = $this->createMock(FileManager::class);
$this->module = new Module($this->fileManager);
}
public function testOrder1(): void
{
$this->fileManager
->expects(self::any())
->method('getDirList')
->willReturnMap([
[
'application/Espo/Modules',
['M01', 'M02', 'M1', 'M2'],
],
[
'custom/Espo/Modules',
['M3', 'M4', 'M51', 'M52'],
]
]);
$this->fileManager
->expects(self::any())
->method('exists')
->willReturnMap([
['application/Espo/Modules/M01/Resources/module.json', true],
['application/Espo/Modules/M02/Resources/module.json', true],
['application/Espo/Modules/M1/Resources/module.json', true],
['application/Espo/Modules/M2/Resources/module.json', true],
['custom/Espo/Modules/M3/Resources/module.json', true],
['custom/Espo/Modules/M4/Resources/module.json', true],
['custom/Espo/Modules/M51/Resources/module.json', true],
['custom/Espo/Modules/M52/Resources/module.json', true],
]);
$this->fileManager
->expects(self::any())
->method('getContents')
->willReturnMap([
['application/Espo/Modules/M01/Resources/module.json', '{"order": 11}'],
['application/Espo/Modules/M02/Resources/module.json', '{"order": 11}'],
['application/Espo/Modules/M1/Resources/module.json', '{"order": 4}'],
['application/Espo/Modules/M2/Resources/module.json', '{"order": 3}'],
['custom/Espo/Modules/M3/Resources/module.json', '{"order": 2}'],
['custom/Espo/Modules/M4/Resources/module.json', '{"order": 1}'],
['custom/Espo/Modules/M51/Resources/module.json', '{"order": 12}'],
['custom/Espo/Modules/M52/Resources/module.json', '{"order": 12}'],
]);
$this->assertEquals(
['M4', 'M3', 'M2', 'M1', 'M01', 'M02', 'M51', 'M52'],
$this->module->getOrderedList()
);
}
}

View File

@@ -0,0 +1,68 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\Espo\Core\Utils;
use Espo\Core\Utils\ObjectUtil;
class ObjectUtilTest extends \PHPUnit\Framework\TestCase
{
public function testClone1()
{
$original = (object) [
'key1' => '1',
'key2' => (object) [
'key21' => [
'211',
'212',
(object) [
'2111' => '1',
],
],
],
'key3' => [
'31',
'32',
null,
],
'key4' => null,
];
$cloned = ObjectUtil::clone($original);
$this->assertEquals($cloned, $original);
$this->assertNotSame($cloned, $original);
$this->assertNotSame($cloned->key2, $original->key2);
$this->assertNotSame($cloned->key2->key21[2], $original->key2->key21[2]);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\Preload;
class PreloadTest extends \PHPUnit\Framework\TestCase
{
public function testPreload()
{
$preload = new Preload();
$preload->process();
$this->assertTrue($preload->getCount() > 0);
}
}

View File

@@ -0,0 +1,101 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Resource;
use Espo\Core\Utils\Resource\Reader;
use Espo\Core\Utils\Resource\Reader\Params as ReaderParams;
use Espo\Core\Utils\File\Unifier;
use Espo\Core\Utils\File\UnifierObj;
class ReaderTest extends \PHPUnit\Framework\TestCase
{
/**
* @var Reader
*/
private $reader;
/**
* @var Unifier
*/
private $unifier;
/**
* @var UnifierObj
*/
private $unifierObj;
protected function setUp(): void
{
$this->unifier = $this->createMock(Unifier::class);
$this->unifierObj = $this->createMock(UnifierObj::class);
$this->reader = new Reader($this->unifier, $this->unifierObj);
}
public function testRead1(): void
{
$params = ReaderParams::create();
$this->unifierObj
->expects($this->once())
->method('unify')
->with('test/hello', false)
->willReturn((object) []);
$this->reader->read('test/hello', $params);
}
public function testRead2(): void
{
$params = ReaderParams::create();
$this->unifier
->expects($this->once())
->method('unify')
->with('test/hello', false)
->willReturn([]);
$this->reader->readAsArray('test/hello', $params);
}
public function testRead3(): void
{
$params = ReaderParams::create()
->withNoCustom();
$this->unifier
->expects($this->once())
->method('unify')
->with('test/hello', true)
->willReturn([]);
$this->reader->readAsArray('test/hello', $params);
}
}

View File

@@ -0,0 +1,414 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Api\Route as RouteItem;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Resource\PathProvider;
use Espo\Core\Utils\Route;
use PHPUnit\Framework\TestCase;
class RouteTest extends TestCase
{
private $route;
private $filesPath = 'tests/unit/testData/Routes';
private $pathProvider;
private $metadata;
protected function setUp(): void
{
$fileManager = new FileManager();
$this->metadata = $this->createMock(Metadata::class);
$dataCache = $this->createMock(DataCache::class);
$this->pathProvider = $this->createMock(PathProvider::class);
$this->route = new Route(
$this->metadata,
$fileManager,
$dataCache,
$this->pathProvider,
$this->createMock(Config\SystemConfig::class),
);
}
private function initPathProvider(string $folder): void
{
$this->pathProvider
->method('getCustom')
->willReturn($this->filesPath . '/' . $folder . '/custom/Espo/Custom/Resources/');
$this->pathProvider
->method('getCore')
->willReturn($this->filesPath . '/' . $folder . '/application/Espo/Resources/');
$this->pathProvider
->method('getModule')
->willReturnCallback(
function (?string $moduleName) use ($folder): string {
$path = $this->filesPath . '/' . $folder . '/application/Espo/Modules/{*}/Resources/';
if ($moduleName === null) {
return $path;
}
return str_replace('{*}', $moduleName, $path);
}
);
}
public function testUnifyCase1CustomRoutes()
{
$this->initPathProvider('testCase1');
$this->metadata
->expects($this->once())
->method('getModuleList')
->willReturn(
['Crm']
);
$expected = [
[
'adjustedRoute' => '/Custom/{scope}/{id}/{name}',
'route' => '/Custom/:scope/:id/:name',
'method' => 'get',
'params' =>
[
'controller' => 'Custom',
'action' => 'list',
'scope' => ':scope',
'id' => ':id',
'name' => ':name',
],
],
[
'adjustedRoute' => '/Test',
'route' => '/Test',
'method' => 'get',
'params' =>
[
'controller' => 'TestOverridden',
],
],
[
'adjustedRoute' => '/Activities/{scope}/{id}/{name}',
'route' => '/Activities/:scope/:id/:name',
'method' => 'get',
'params' =>
[
'controller' => 'Activities',
'action' => 'list',
'scope' => ':scope',
'id' => ':id',
'name' => ':name',
],
],
[
'adjustedRoute' => '/Activities',
'route' => '/Activities',
'method' => 'get',
'params' =>
[
'controller' => 'Activities',
'action' => 'listCalendarEvents',
],
],
[
'adjustedRoute' => '/App/user',
'route' => '/App/user',
'method' => 'get',
'params' =>
[
'controller' => 'App',
'action' => 'user',
],
],
[
'adjustedRoute' => '/Metadata',
'route' => '/Metadata',
'method' => 'get',
'params' =>
[
'controller' => 'Metadata',
],
],
[
'adjustedRoute' => '/{controller}/action/{action}',
'route' => '/:controller/action/:action',
'method' => 'post',
'params' =>
[
'controller' => ':controller',
'action' => ':action',
],
],
[
'adjustedRoute' => '/{controller}/action/{action}',
'route' => '/:controller/action/:action',
'method' => 'get',
'params' =>
[
'controller' => ':controller',
'action' => ':action',
],
],
];
$expectedItemList = array_map(
function (array $item) {
return new RouteItem(
method: $item['method'],
route: $item['route'],
adjustedRoute: $item['adjustedRoute'],
params: $item['params'] ?? [],
noAuth: $item['noAuth'] ?? false,
consumes: null,
actionClassName: null,
);
},
$expected
);
$this->assertEquals($expectedItemList, $this->route->getFullList());
}
public function testUnifyCase2ModuleRoutes()
{
$this->initPathProvider('testCase2');
$this->metadata
->expects($this->once())
->method('getModuleList')
->willReturn(
['Crm', 'Test']
);
$expected = [
[
'adjustedRoute' => '/Test',
'route' => '/Test',
'method' => 'get',
'params' =>
[
'controller' => 'Test',
'action' => 'listCalendarEvents',
],
],
[
'adjustedRoute' => '/Activities/{scope}/{id}/{name}',
'route' => '/Activities/:scope/:id/:name',
'method' => 'get',
'params' =>
[
'controller' => 'Activities',
'action' => 'list',
'scope' => ':scope',
'id' => ':id',
'name' => ':name',
],
],
[
'adjustedRoute' => '/Activities',
'route' => '/Activities',
'method' => 'get',
'params' =>
[
'controller' => 'Activities',
'action' => 'listCalendarEvents',
],
],
[
'adjustedRoute' => '/App/user',
'route' => '/App/user',
'method' => 'get',
'params' =>
[
'controller' => 'App',
'action' => 'user',
],
],
[
'adjustedRoute' => '/Metadata',
'route' => '/Metadata',
'method' => 'get',
'params' =>
[
'controller' => 'Metadata',
],
],
[
'adjustedRoute' => '/{controller}/action/{action}',
'route' => '/:controller/action/:action',
'method' => 'post',
'params' =>
[
'controller' => ':controller',
'action' => ':action',
],
],
[
'adjustedRoute' => '/{controller}/action/{action}',
'route' => '/:controller/action/:action',
'method' => 'get',
'params' =>
[
'controller' => ':controller',
'action' => ':action',
],
],
];
$expectedItemList = array_map(
function (array $item) {
return new RouteItem(
method: $item['method'],
route: $item['route'],
adjustedRoute: $item['adjustedRoute'],
params: $item['params'] ?? [],
noAuth: $item['noAuth'] ?? false,
consumes: null,
actionClassName: null,
);
},
$expected
);
$this->assertEquals($expectedItemList, $this->route->getFullList());
}
public function testUnifyCase3ModuleRoutesWithRewrites()
{
$this->initPathProvider('testCase3');
$this->metadata
->expects($this->once())
->method('getModuleList')
->willReturn(
['Crm', 'Test']
);
$expected = [
[
'adjustedRoute' => '/Activities/{scope}/{id}/{name}',
'route' => '/Activities/:scope/:id/:name',
'method' => 'get',
'params' =>
[
'controller' => 'Test',
'action' => 'list',
'scope' => ':scope',
'id' => ':id',
'name' => ':name',
],
],
[
'adjustedRoute' => '/Test',
'route' => '/Test',
'method' => 'get',
'params' =>
[
'controller' => 'Test',
'action' => 'listCalendarEvents',
],
],
[
'adjustedRoute' => '/Activities',
'route' => '/Activities',
'method' => 'get',
'params' =>
[
'controller' => 'Activities',
'action' => 'listCalendarEvents',
],
],
[
'adjustedRoute' => '/App/user',
'route' => '/App/user',
'method' => 'get',
'params' =>
[
'controller' => 'App',
'action' => 'user',
],
],
[
'adjustedRoute' => '/Metadata',
'route' => '/Metadata',
'method' => 'get',
'params' =>
[
'controller' => 'Metadata',
],
],
[
'adjustedRoute' => '/{controller}/action/{action}',
'route' => '/:controller/action/:action',
'method' => 'post',
'params' =>
[
'controller' => ':controller',
'action' => ':action',
],
],
[
'adjustedRoute' => '/{controller}/action/{action}',
'route' => '/:controller/action/:action',
'method' => 'get',
'params' =>
[
'controller' => ':controller',
'action' => ':action',
],
],
];
$expectedItemList = array_map(
function (array $item) {
return new RouteItem(
method: $item['method'],
route: $item['route'],
adjustedRoute: $item['adjustedRoute'],
params: $item['params'] ?? [],
noAuth: $item['noAuth'] ?? false,
consumes: null,
actionClassName: null,
);
},
$expected
);
$this->assertEquals($expectedItemList, $this->route->getFullList());
}
}

View File

@@ -0,0 +1,103 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Security;
use Espo\Core\Utils\Security\HostCheck;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
class HostCheckTest extends TestCase
{
public function testIsHostAndNotInternal(): void
{
$hostCheck = new HostCheck();
$this->assertTrue(
$hostCheck->isHostAndNotInternal('200.1.1.1')
);
$this->assertFalse(
$hostCheck->isHostAndNotInternal('172.20.0.1')
);
$this->assertFalse(
$hostCheck->isHostAndNotInternal('0177.0.0.1')
);
$this->assertFalse(
$hostCheck->isHostAndNotInternal('127.1')
);
$this->assertFalse(
$hostCheck->isHostAndNotInternal('127.0.1')
);
$this->assertFalse(
$hostCheck->isHostAndNotInternal('2130706433')
);
$this->assertFalse(
$hostCheck->isHostAndNotInternal('0x7f000001')
);
$this->assertFalse(
$hostCheck->isHostAndNotInternal('0x7f.1')
);
}
public static function ipAddressTestProvider(): array
{
return [
['200.1.1.1', true],
['172.20.0.1', false],
['2606:2800:220:1:248:1893:25c8:1946', true],
['::ffff:127.0.0.1', false],
['64:ff9b::a00:1', false],
['2002:0a00:0001::', false],
['2001:0000:4136:e378:8000:0000:f5ff:fffe', false],
['2001:4860:4860::8888', true],
['2606:4700:4700::1111', true],
['2620:fe::fe', true],
['2404:6800:4004:80c::200e', true],
['2a09:bac1:1234::1', true],
['fe80::1', false],
['fc00::1', false],
['::1', false],
];
}
#[DataProvider('ipAddressTestProvider')]
public function testIpAddress(string $ipAddress, bool $expected)
{
$hostCheck = new HostCheck();
$this->assertEquals($expected, $hostCheck->ipAddressIsNotInternal($ipAddress));
}
}

View File

@@ -0,0 +1,86 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils\Security;
use Espo\Core\Utils\Security\HostCheck;
use Espo\Core\Utils\Security\UrlCheck;
use PHPUnit\Framework\TestCase;
class UrlCheckTest extends TestCase
{
public function testGetCurlResolveDual(): void
{
$hostCheck = $this->createMock(HostCheck::class);
$urlCheck = new UrlCheck($hostCheck);
$url = 'https://test.com';
$hostCheck->method('isDomainHost')
->with('test.com')
->willReturn(true);
$hostCheck->method('getHostIpAddresses')
->with('test.com')
->willReturn([
'10.0.0.1',
'10.0.0.2',
'2001:0db8:85a3:0000:0000:8a2e:0370:7334',
]);
$this->assertEquals([
'test.com:443:10.0.0.1',
'test.com:443:10.0.0.2',
], $urlCheck->getCurlResolve($url));
}
public function testGetCurlResolveIpV6Only(): void
{
$hostCheck = $this->createMock(HostCheck::class);
$urlCheck = new UrlCheck($hostCheck);
$url = 'https://test.com';
$hostCheck->method('isDomainHost')
->with('test.com')
->willReturn(true);
$hostCheck->method('getHostIpAddresses')
->with('test.com')
->willReturn([
'2001:0db8:85a3:0000:0000:8a2e:0370:7334',
]);
$this->assertEquals([
'test.com:443:[2001:0db8:85a3:0000:0000:8a2e:0370:7334]',
], $urlCheck->getCurlResolve($url));
}
}

View File

@@ -0,0 +1,102 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\Util;
use Espo\Core\Utils\System;
class SystemTest extends \PHPUnit\Framework\TestCase
{
protected $object;
protected $reflection;
protected function setUp() : void
{
$this->object = new \Espo\Core\Utils\System();
}
protected function tearDown() : void
{
$this->object = NULL;
}
public function testGetServerType()
{
$_SERVER['SERVER_SOFTWARE'] = 'Apache/2.2.17 (Ubuntu)';
$this->assertEquals( 'apache', $this->object->getServerType());
$_SERVER['SERVER_SOFTWARE'] = 'Apache';
$this->assertEquals( 'apache', $this->object->getServerType());
$_SERVER['SERVER_SOFTWARE'] = 'nginx/1.5.12';
$this->assertEquals( 'nginx', $this->object->getServerType());
$_SERVER['SERVER_SOFTWARE'] = 'Microsoft-IIS/8.0';
$this->assertEquals( 'microsoft-iis', $this->object->getServerType());
$_SERVER['SERVER_SOFTWARE'] = 'apache/2.4.10 (win32) openssl/1.0.1i php';
$this->assertEquals( 'apache', $this->object->getServerType());
}
public function testGetOS()
{
$possibleValues = array(
'windows',
'mac',
'linux',
);
$this->assertTrue( in_array($this->object->getOS(), $possibleValues));
}
public function testGetRootDir()
{
$rootDir = dirname(__FILE__);
$rootDir = str_replace(Util::fixPath('/tests/unit/Espo/Core/Utils'),'',$rootDir);
$this->assertEquals($rootDir, $this->object->getRootDir());
}
public function testGetPhpBinary()
{
$phpBinary = (new \Symfony\Component\Process\PhpExecutableFinder)->find();
if (isset($phpBinary)) {
$this->assertEquals($phpBinary, $this->object->getPhpBinary());
}
}
public function testGetPhpVersion()
{
$this->assertTrue( (bool) preg_match('/^[0-9\.]+$/', System::getPhpVersion()) );
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace tests\unit\Espo\Core\Utils;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Theme\Direction;
use Espo\Core\Utils\Theme\MetadataProvider;
use Espo\Core\Utils\ThemeManager;
use PHPUnit\Framework\TestCase;
class ThemeManagerTest extends TestCase
{
public function testDirectionFromConfig(): void
{
$manager = $this->createManager(
configValues: [
'theme' => 'Espo',
'themeParams.direction' => 'rtl',
],
);
$this->assertSame(Direction::Rtl, $manager->getDirection());
}
public function testDirectionFromThemeDefault(): void
{
$manager = $this->createManager(
configValues: [
'theme' => 'Custom',
'themeParams.direction' => 'invalid',
],
metadataValues: [
'themes.Custom.params.direction.default' => 'rtl',
],
);
$this->assertSame(Direction::Rtl, $manager->getDirection());
}
public function testInvalidDirectionFallsBackToLtr(): void
{
$manager = $this->createManager(
configValues: [
'theme' => 'Espo',
'themeParams.direction' => 'invalid',
],
metadataValues: [
'themes.Espo.params.direction.default' => 'invalid',
],
);
$this->assertSame(Direction::Ltr, $manager->getDirection());
}
/**
* @param array<string, mixed> $configValues
* @param array<string, mixed> $metadataValues
*/
private function createManager(array $configValues, array $metadataValues = []): ThemeManager
{
$config = $this->createMock(Config::class);
$config
->method('get')
->willReturnCallback(fn (string $key) => $configValues[$key] ?? null);
$metadata = $this->createMock(Metadata::class);
$metadata
->method('get')
->willReturnCallback(function (array|string $path) use ($metadataValues) {
$key = is_array($path) ? implode('.', $path) : $path;
return $metadataValues[$key] ?? null;
});
return new ThemeManager(
$config,
$metadata,
$this->createMock(MetadataProvider::class),
);
}
}

File diff suppressed because it is too large Load Diff