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 1s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 1s
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,88 @@
<?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;
use Espo\Core\Container;
use PHPUnit\Framework\MockObject\MockBuilder;
use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
class ContainerMocker
{
protected $test;
public function __construct(TestCase $test)
{
$this->test = $test;
}
public function create(array $serviceMap) : Container
{
$container = (new MockBuilder($this->test, Container::class))->disableOriginalConstructor()->getMock();
$map = $serviceMap;
$valueMap = [];
$hasMap = [];
$classMap = [];
foreach ($map as $key => $value) {
$valueMap[] = [$key, $value];
}
foreach ($map as $key => $value) {
$hasMap[] = [$key, true];
}
foreach ($map as $key => $value) {
$classMap[] = [$key, new ReflectionClass($value)];
}
$container
->expects(new AnyInvokedCountMatcher)
->method('get')
->willReturnMap($valueMap);
$container
->expects(new AnyInvokedCountMatcher)
->method('has')
->willReturnMap($hasMap);
$container
->expects(new AnyInvokedCountMatcher)
->method('getClass')
->willReturnMap($classMap);
return $container;
}
}

View File

@@ -0,0 +1,63 @@
<?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\Classes\FieldSanitizers;
use Espo\Classes\FieldSanitizers\ArrayStringTrim;
use Espo\Core\FieldSanitize\Sanitizer\Data;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
class ArrayStringTrimTest extends TestCase
{
/**
* @param string[] $expected
* @param string[] $input
* @return void
*/
#[DataProvider('provider')]
public function testTrim(array $expected, array $input): void
{
$data = new Data((object) ['test' => $input]);
(new ArrayStringTrim())->sanitize($data, 'test');
$this->assertEquals($expected, $data->get('test'));
}
/**
* @return array{string[], string[]}[]
*/
public static function provider(): array
{
return [
[['test'], [' test ']],
];
}
}

View File

@@ -0,0 +1,55 @@
<?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\Classes\FieldSanitizers;
use Espo\Classes\FieldSanitizers\StringTrim;
use Espo\Core\FieldSanitize\Sanitizer\Data;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
class StringTrimTest extends TestCase
{
#[DataProvider('provider')]
public function testTrim(string $expected, string $input): void
{
$data = new Data((object) ['test' => $input]);
(new StringTrim())->sanitize($data, 'test');
$this->assertEquals($expected, $data->get('test'));
}
public static function provider(): array
{
return [
['test', ' test '],
];
}
}

View File

@@ -0,0 +1,495 @@
<?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\Commands;
use Closure;
use Espo\Classes\ConsoleCommands\GetConfigParam;
use Espo\Classes\ConsoleCommands\SetConfigParam;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Utils\Config;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ConfigTest extends TestCase
{
public function testGetParamNoParam(): void
{
$params = new Params(
options: [],
flagList: [],
argumentList: [],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$this->expectIoError($io);
(new GetConfigParam($config))->run($params, $io);
}
public function testGetParamNonExistingParam(): void
{
$params = new Params(
options: [],
flagList: [],
argumentList: ['test'],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$config
->method('has')
->with('test')
->willReturn(false);
$this->expectIoError($io);
(new GetConfigParam($config))->run($params, $io);
}
public function testGetParamExisting(): void
{
$params = new Params(
options: [],
flagList: [],
argumentList: ['test'],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$config
->method('has')
->with('test')
->willReturn(true);
$config
->method('get')
->with('test')
->willReturn('hello');
$this->expectIoNoError($io);
$this->expectWriteLine($io, 'hello');
(new GetConfigParam($config))->run($params, $io);
}
public function testGetParamJson(): void
{
$params = new Params(
options: [],
flagList: ['json'],
argumentList: ['test'],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$config
->method('has')
->with('test')
->willReturn(true);
$config
->method('get')
->with('test')
->willReturn('hello');
$this->expectIoNoError($io);
$this->expectWriteLine($io, '"hello"');
(new GetConfigParam($config))->run($params, $io);
}
public function testGetParamTrue(): void
{
$params = new Params(
options: [],
flagList: [],
argumentList: ['test'],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$config
->method('has')
->with('test')
->willReturn(true);
$config
->method('get')
->with('test')
->willReturn(true);
$this->expectIoNoError($io);
$this->expectWriteLine($io, 'true');
(new GetConfigParam($config))->run($params, $io);
}
public function testGetParamFalse(): void
{
$params = new Params(
options: [],
flagList: [],
argumentList: ['test'],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$config
->method('has')
->with('test')
->willReturn(true);
$config
->method('get')
->with('test')
->willReturn(false);
$this->expectIoNoError($io);
$this->expectWriteLine($io, 'false');
(new GetConfigParam($config))->run($params, $io);
}
public function testGetParamNull(): void
{
$params = new Params(
options: [],
flagList: [],
argumentList: ['test'],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$config
->method('has')
->with('test')
->willReturn(true);
$config
->method('get')
->with('test')
->willReturn(null);
$this->expectIoNoError($io);
$this->expectWriteLine($io, 'null');
(new GetConfigParam($config))->run($params, $io);
}
private function expectIoError(IO & MockObject $io): void
{
$io->expects($this->once())
->method('setExitStatus')
->with(1);
}
private function expectIoNoError(IO & MockObject $io): void
{
$io->expects($this->never())
->method('setExitStatus');
}
private function expectWriteLine(IO & MockObject $io, string $value): void
{
$io->expects($this->once())
->method('writeLine')
->with($value);
}
public function testSetConfigNoParam(): void
{
$params = new Params(
options: [],
flagList: [],
argumentList: [],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$configWriter = $this->createMock(Config\ConfigWriter::class);
$this->expectIoError($io);
(new SetConfigParam($config, $configWriter))->run($params, $io);
}
public function testSetConfigNoValue(): void
{
$params = new Params(
options: [],
flagList: [],
argumentList: ['test'],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$configWriter = $this->createMock(Config\ConfigWriter::class);
$this->expectIoError($io);
(new SetConfigParam($config, $configWriter))->run($params, $io);
}
public function testSetConfigValueString1(): void
{
$this->internalTestSet(
type: null,
paramName: 'test',
value: 'hello',
valueExpected: 'hello',
);
}
public function testSetConfigValueString2(): void
{
$this->internalTestSet(
type: 'string',
paramName: 'test',
value: 'hello',
valueExpected: 'hello',
);
}
public function testSetConfigValueBool1(): void
{
$this->internalTestSet(
type: 'bool',
paramName: 'test',
value: '1',
valueExpected: true,
);
}
public function testSetConfigValueBool2(): void
{
$this->internalTestSet(
type: 'bool',
paramName: 'test',
value: '0',
valueExpected: false,
);
}
public function testSetConfigValueBool3(): void
{
$this->internalTestSet(
type: 'bool',
paramName: 'test',
value: 'true',
valueExpected: true,
);
}
public function testSetConfigValueBool4(): void
{
$this->internalTestSet(
type: 'bool',
paramName: 'test',
value: 'false',
valueExpected: false,
);
}
public function testSetConfigValueBoolBad(): void
{
$this->internalTestSet(
type: 'bool',
paramName: 'test',
value: 'yes',
error: true,
);
}
public function testSetConfigValueJson(): void
{
$this->internalTestSet(
type: 'json',
paramName: 'test',
value: '"true"',
valueExpected: "true",
);
}
public function testSetConfigValueObject1(): void
{
$this->internalTestSet(
type: null,
paramName: 'database.host',
value: 'hello',
valueExpected: ['host' => 'hello', 'port' => 0],
hook: function (Config & MockObject $config) {
$config
->expects($this->once())
->method('get')
->with('database')
->willReturn(['host' => 'localhost', 'port' => 0]);
},
setParamNameExpected: 'database',
);
}
public function testSetConfigValueAutoTrue1(): void
{
$this->internalTestSet(
type: 'auto',
paramName: 'test',
value: 'true',
valueExpected: true,
);
}
public function testSetConfigValueAutoTrue2(): void
{
$this->internalTestSet(
type: 'auto',
paramName: 'test',
value: 'TRUE',
valueExpected: true,
);
}
public function testSetConfigValueAutoFalse1(): void
{
$this->internalTestSet(
type: 'auto',
paramName: 'test',
value: 'false',
valueExpected: false,
);
}
public function testSetConfigValueAutoNull1(): void
{
$this->internalTestSet(
type: 'auto',
paramName: 'test',
value: 'null',
);
}
public function testSetConfigValueAutoNull2(): void
{
$this->internalTestSet(
type: 'auto',
paramName: 'test',
value: 'NULL',
);
}
public function testSetConfigValueAutoInt(): void
{
$this->internalTestSet(
type: 'auto',
paramName: 'test',
value: '2',
valueExpected: 2,
);
}
public function testSetConfigValueAutoFloat(): void
{
$this->internalTestSet(
type: 'auto',
paramName: 'test',
value: '2.0',
valueExpected: 2.0,
);
}
public function testSetConfigValueAutoString(): void
{
$this->internalTestSet(
type: 'auto',
paramName: 'test',
value: 'value',
valueExpected: 'value',
);
}
/**
* @param (Closure(Config & MockObject): void)|null $hook
*/
private function internalTestSet(
?string $type,
string $paramName,
string $value,
mixed $valueExpected = null,
bool $error = false,
?Closure $hook = null,
?string $setParamNameExpected = null,
): void {
$options = [];
if ($type !== null) {
$options = ['type' => $type];
}
$params = new Params(
options: $options,
flagList: [],
argumentList: [$paramName, $value],
);
$io = $this->createMock(IO::class);
$config = $this->createMock(Config::class);
$configWriter = $this->createMock(Config\ConfigWriter::class);
if ($hook) {
$hook($config);
}
if (!$error) {
$this->expectIoNoError($io);
$configWriter
->expects($this->once())
->method('save');
$configWriter
->expects($this->once())
->method('set')
->with($setParamNameExpected ?? $paramName, $valueExpected);
} else {
$this->expectIoError($io);
}
(new SetConfigParam($config, $configWriter))->run($params, $io);
}
}

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\Acl;
use Espo\Core\{
Acl\FieldData,
Acl\Table,
};
class FieldDataTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
}
public function testGet1(): void
{
$raw = (object) [
Table::ACTION_EDIT => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_NO,
];
$data = FieldData::fromRaw($raw);
$this->assertEquals(Table::LEVEL_NO, $data->getRead());
$this->assertEquals(Table::LEVEL_YES, $data->getEdit());
}
public function testGet2(): void
{
$raw = (object) [
Table::ACTION_EDIT => Table::LEVEL_NO,
Table::ACTION_READ => Table::LEVEL_YES,
];
$data = FieldData::fromRaw($raw);
$this->assertEquals(Table::LEVEL_YES, $data->getRead());
$this->assertEquals(Table::LEVEL_NO, $data->getEdit());
}
public function testGetEmpty(): void
{
$raw = (object) [
];
$data = FieldData::fromRaw($raw);
$this->assertEquals(Table::LEVEL_NO, $data->getRead());
$this->assertEquals(Table::LEVEL_NO, $data->getEdit());
}
}

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\Acl\Map;
use Espo\Core\Acl\Map\DefaultCacheKeyProvider;
use Espo\Entities\User;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
class DefaultCacheKeyProviderTest extends TestCase
{
#[DataProvider('provider')]
public function testKey(string $expected, string $userId, ?array $scopes = null): void
{
$user = $this->createMock(User::class);
$user->expects(self::any())
->method('getId')
->willReturn($userId);
$user->expects(self::any())
->method('getScopes')
->willReturn($scopes);
$provider = new DefaultCacheKeyProvider($user);
$this->assertEquals($expected, $provider->get());
}
public static function provider(): array
{
return [
['aclMap/01', '01', null],
['aclMap/01/37f6e5e382faedc68e0769892e78d8d3', '01', ['Admin', 'Global']],
['aclMap/01/99aa06d3014798d86001c324468d497f', '01', []],
];
}
}

View File

@@ -0,0 +1,427 @@
<?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\Acl\Map;
use Espo\Core\Acl\FieldData;
use Espo\Core\Acl\Map\CacheKeyProvider;
use Espo\Core\Acl\Map\DataBuilder;
use Espo\Core\Acl\Map\Map;
use Espo\Core\Acl\Map\MetadataProvider;
use Espo\Core\Acl\ScopeData;
use Espo\Core\Acl\Table;
use Espo\Core\Utils\Cache\DataCacheAccess;
use Espo\Core\Utils\Config\SystemConfig;
use Espo\Core\Utils\DataCache;
use Espo\Core\Utils\FieldUtil;
use Espo\Core\Utils\Log;
use PHPUnit\Framework\TestCase;
use stdClass;
class MapTest extends TestCase
{
private $fieldUtil;
private $table;
private $metadataProvider;
private $cacheKeyProvider;
protected function setUp(): void
{
$this->fieldUtil = $this->createMock(FieldUtil::class);
$this->table = $this->createMock(Table::class);
$this->metadataProvider = $this->createMock(MetadataProvider::class);
$this->cacheKeyProvider = $this->createMock(CacheKeyProvider::class);
}
private function mockTableData(array $scopeData, array $fieldData, array $permissionData): void
{
$returnMap1 = [];
foreach ($scopeData as $scope => $item) {
$returnMap1[] = [$scope, ScopeData::fromRaw($item)];
}
$this->table
->expects($this->any())
->method('getScopeData')
->willReturnMap($returnMap1);
$returnMap2 = [];
foreach ($fieldData as $scope => $item1) {
foreach ($item1 as $field => $item2) {
$returnMap2[] = [$scope, $field, FieldData::fromRaw($item2)];
}
}
$this->table
->expects($this->any())
->method('getFieldData')
->willReturnMap($returnMap2);
$returnMap3 = [];
foreach ($permissionData as $permission => $level) {
$returnMap3[] = [$permission, $level];
}
$this->table
->expects($this->any())
->method('getPermissionLevel')
->willReturnMap($returnMap3);
}
public function testMap1(): void
{
$dataBuilder = new DataBuilder($this->metadataProvider, $this->fieldUtil);
$this->metadataProvider
->expects($this->any())
->method('getScopeList')
->willReturn(['Test1', 'Test2', 'Test3', 'Test4', 'Test5']);
$this->metadataProvider
->expects($this->any())
->method('getPermissionList')
->willReturn(['assignment', 'portal']);
$this->metadataProvider
->expects($this->any())
->method('isScopeEntity')
->willReturnMap([
['Test1', true],
['Test2', true],
['Test3', true],
['Test4', true],
['Test5', false],
]);
$this->metadataProvider
->expects($this->any())
->method('getScopeFieldList')
->willReturnMap([
['Test1', ['field1', 'field2', 'field3', 'field4']],
['Test2', ['field1']],
['Test3', []],
['Test4', []],
['Test5', []],
]);
$this->fieldUtil
->expects($this->any())
->method('getAttributeList')
->willReturnMap([
['Test1', 'field1', ['attr1a', 'attr1b']],
['Test1', 'field2', ['field2']],
['Test1', 'field3', ['field3']],
['Test1', 'field4', ['field4']],
['Test2', 'field1', ['field1']],
]);
$this->mockTableData(
[
'Test1' => (object) [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_TEAM,
],
'Test2' => (object) [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_TEAM,
Table::ACTION_EDIT => Table::LEVEL_OWN,
],
'Test3' => false,
'Test4' => true,
'Test5' => true,
],
[
'Test1' => [
'field1' => (object) [
Table::ACTION_READ => Table::LEVEL_YES,
Table::ACTION_EDIT => Table::LEVEL_NO,
],
'field2' => (object) [
Table::ACTION_READ => Table::LEVEL_NO,
Table::ACTION_EDIT => Table::LEVEL_YES,
],
'field3' => (object) [
Table::ACTION_READ => Table::LEVEL_NO,
Table::ACTION_EDIT => Table::LEVEL_NO,
],
'field4' => (object) [
Table::ACTION_READ => Table::LEVEL_YES,
Table::ACTION_EDIT => Table::LEVEL_YES,
],
],
'Test2' => [
'field1' => (object) [
Table::ACTION_READ => Table::LEVEL_NO,
Table::ACTION_EDIT => Table::LEVEL_NO,
],
],
],
[
'assignment' => Table::LEVEL_YES,
'portal' => Table::LEVEL_NO,
],
);
$expectedData = $this->getExpectedRawData();
$this->cacheKeyProvider->method('get')
->willReturn('key');
$dataCacheAccess = new DataCacheAccess(
dataCache: $this->createMock(DataCache::class),
systemConfig: $this->createMock(SystemConfig::class),
log: $this->createMock(Log::class),
);
$map = new Map(
table: $this->table,
dataBuilder: $dataBuilder,
cacheKeyProvider: $this->cacheKeyProvider,
dataCacheAccess: $dataCacheAccess,
);
$this->assertEquals($expectedData, $map->getData());
$this->assertEquals(
['field2', 'field3'],
$map->getScopeForbiddenFieldList('Test1', Table::ACTION_READ)
);
$this->assertEquals(
['field1', 'field3'],
$map->getScopeForbiddenFieldList('Test1', Table::ACTION_EDIT)
);
$this->assertEquals(
['field2', 'field3'],
$map->getScopeForbiddenAttributeList('Test1', Table::ACTION_READ)
);
$this->assertEquals(
['attr1a', 'attr1b', 'field3'],
$map->getScopeForbiddenAttributeList('Test1', Table::ACTION_EDIT)
);
$this->assertEquals(
['attr1a', 'attr1b', 'field3'],
$map->getScopeForbiddenAttributeList('Test1', Table::ACTION_EDIT)
);
$this->assertEquals(
['field1'],
$map->getScopeForbiddenFieldList('Test2', Table::ACTION_READ)
);
$this->assertEquals(
['field1'],
$map->getScopeForbiddenFieldList('Test2', Table::ACTION_READ)
);
$this->assertEquals(
[],
$map->getScopeForbiddenFieldList('Test3', Table::ACTION_READ)
);
}
private function getExpectedRawData(): stdClass
{
return (object) [
'table' => (object) [
'Test1' => (object) [
'read' => 'team',
'stream' => 'no',
'edit' => 'no',
'delete' => 'no',
'create' => 'yes'
],
'Test2' => (object) [
'read' => 'team',
'stream' => 'no',
'edit' => 'own',
'delete' => 'no',
'create' => 'yes'
],
'Test3' => false,
'Test4' => true,
'Test5' => true
],
'fieldTable' => (object) [
'Test1' => (object) [
'field1' => (object) [
'read' => 'yes',
'edit' => 'no'
],
'field2' => (object) [
'read' => 'no',
'edit' => 'yes'
],
'field3' => (object) [
'read' => 'no',
'edit' => 'no'
]
],
'Test2' => (object) [
'field1' => (object) [
'read' => 'no',
'edit' => 'no'
]
],
'Test3' => (object) [],
'Test4' => (object) []
],
'assignmentPermission' => 'yes',
'portalPermission' => 'no',
'fieldTableQuickAccess' => (object) [
'Test1' => (object) [
'attributes' => (object) [
'read' => (object) [
'yes' => [
'attr1a',
'attr1b'
],
'no' => [
'field2',
'field3'
]
],
'edit' => (object) [
'yes' => [
'field2'
],
'no' => [
'attr1a',
'attr1b',
'field3'
]
]
],
'fields' => (object) [
'read' => (object) [
'yes' => [
'field1'
],
'no' => [
'field2',
'field3'
]
],
'edit' => (object) [
'yes' => [
'field2'
],
'no' => [
'field1',
'field3'
]
]
]
],
'Test2' => (object) [
'attributes' => (object) [
'read' => (object) [
'yes' => [],
'no' => [
'field1'
]
],
'edit' => (object) [
'yes' => [],
'no' => [
'field1'
]
]
],
'fields' => (object) [
'read' => (object) [
'yes' => [],
'no' => [
'field1'
]
],
'edit' => (object) [
'yes' => [],
'no' => [
'field1'
]
]
]
],
'Test3' => (object) [
'attributes' => (object) [
'read' => (object) [
'yes' => [],
'no' => []
],
'edit' => (object) [
'yes' => [],
'no' => []
]
],
'fields' => (object) [
'read' => (object) [
'yes' => [],
'no' => []
],
'edit' => (object) [
'yes' => [],
'no' => []
]
]
],
'Test4' => (object) [
'attributes' => (object) [
'read' => (object) [
'yes' => [],
'no' => []
],
'edit' => (object) [
'yes' => [],
'no' => []
]
],
'fields' => (object) [
'read' => (object) [
'yes' => [],
'no' => []
],
'edit' => (object) [
'yes' => [],
'no' => []
]
]
]
]
];
}
}

View File

@@ -0,0 +1,58 @@
<?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\Acl\Map;
use Espo\Core\Acl\Map\MetadataProvider;
use Espo\Core\Acl\Permission;
use Espo\Core\Utils\Metadata;
use PHPUnit\Framework\TestCase;
class MetadataProviderTest extends TestCase
{
private $metadata;
protected function setUp(): void
{
$this->metadata = $this->createMock(Metadata::class);
}
public function testGetPermissionList(): void
{
$provider = new MetadataProvider($this->metadata);
$this->metadata
->expects($this->once())
->method('get')
->with(['app', 'acl', 'valuePermissionList'])
->willReturn(['assignmentPermission', 'portalPermission']);
$this->assertEquals([Permission::ASSIGNMENT, 'portal'], $provider->getPermissionList());
}
}

View File

@@ -0,0 +1,113 @@
<?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\Acl;
use Espo\Core\Acl\AccessChecker\ScopeCheckerData;
use PHPUnit\Framework\TestCase;
class ScopeCheckerDataTest extends TestCase
{
protected function setUp() : void
{
}
public function testCheckerData0()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$this->assertFalse($checkerData->isOwn());
$this->assertFalse($checkerData->inTeam());
$this->assertFalse($checkerData->isShared());
}
public function testCheckerData1()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInTeam(true)
->setIsShared(true)
->build();
$this->assertTrue($checkerData->isOwn());
$this->assertTrue($checkerData->inTeam());
$this->assertTrue($checkerData->isShared());
}
public function testCheckerData2()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeamChecker(
function (): bool {
return true;
}
)
->setIsSharedChecker(
function (): bool {
return true;
}
)
->build();
$this->assertFalse($checkerData->isOwn());
$this->assertTrue($checkerData->inTeam());
$this->assertTrue($checkerData->isShared());
}
public function testCheckerData3()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwnChecker(
function (): bool {
return true;
}
)
->setInTeamChecker(
function (): bool {
return false;
}
)
->setIsSharedChecker(
function (): bool {
return false;
}
)
->build();
$this->assertTrue($checkerData->isOwn());
$this->assertFalse($checkerData->inTeam());
$this->assertFalse($checkerData->isShared());
}
}

View File

@@ -0,0 +1,435 @@
<?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\Acl;
use Espo\Core\{
Acl\AccessChecker\ScopeChecker,
Acl\AccessChecker\ScopeCheckerData,
Acl\ScopeData,
Acl\Table,
};
class ScopeCheckerTest extends \PHPUnit\Framework\TestCase
{
/**
* @var ScopeChecker
*/
private $scopeChecker;
protected function setUp() : void
{
$this->scopeChecker = new ScopeChecker();
}
public function testCheckerNoData1()
{
$data = ScopeData::fromRaw(false);
$result = $this->scopeChecker->check($data);
$this->assertEquals(false, $result);
}
public function testCheckerNoData2()
{
$data = ScopeData::fromRaw(true);
$result = $this->scopeChecker->check($data);
$this->assertEquals(true, $result);
}
public function testCheckerNoData3()
{
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_NO,
],
);
$result = $this->scopeChecker->check($data);
$this->assertEquals(true, $result);
}
public function testCheckerNoData4()
{
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ALL,
],
);
$result = $this->scopeChecker->check($data);
$this->assertEquals(true, $result);
}
public function testCheckerNoData5()
{
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_TEAM,
],
);
$result = $this->scopeChecker->check($data);
$this->assertEquals(true, $result);
}
public function testCheckerActionNoData1()
{
$data = ScopeData::fromRaw(false);
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE);
$this->assertEquals(false, $result);
}
public function testCheckerActionNoData2()
{
$data = ScopeData::fromRaw(true);
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE);
$this->assertEquals(true, $result);
}
public function testCheckerData1()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeam(false)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ALL,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData2()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeam(false)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_TEAM,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData3()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeam(false)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_OWN,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData4()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeam(false)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_NO,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData5()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeam(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_OWN,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData6()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeam(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_TEAM,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData7()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInTeam(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_TEAM,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData8()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeam(false)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_CREATE => Table::LEVEL_YES,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData9()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInTeam(false)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_OWN,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData10()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInTeam(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_OWN,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData11()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInTeam(false)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_TEAM,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData12()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInTeam(true)
->build();
$data = ScopeData::fromRaw(
(object) [],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData13()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeam(false)
->build();
$data = ScopeData::fromRaw(false);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerDataNoAction1()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInTeam(true)
->build();
$data = ScopeData::fromRaw(false);
$result = $this->scopeChecker->check($data, null, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerDataNoAction2()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInTeam(false)
->build();
$data = ScopeData::fromRaw(true);
$result = $this->scopeChecker->check($data, null, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerDataNoAction3()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInTeam(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_TEAM,
],
);
$result = $this->scopeChecker->check($data, null, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerDataNoAction4()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInTeam(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_CREATE => Table::LEVEL_NO,
Table::ACTION_READ => Table::LEVEL_NO,
],
);
$result = $this->scopeChecker->check($data, null, $checkerData);
$this->assertEquals(true, $result);
}
}

View File

@@ -0,0 +1,106 @@
<?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\Acl;
use Espo\Core\Acl\ScopeData;
use Espo\Core\Acl\Table;
use PHPUnit\Framework\TestCase;
class ScopeDataResolverTest extends TestCase
{
public function testResolve1(): void
{
$table = $this->createMock(Table::class);
$resolver = new Table\ScopeDataResolver($table);
$table->expects($this->once())
->method('getScopeData')
->with('Test')
->willReturn(ScopeData::fromRaw(true));
$this->assertTrue($resolver->resolve('Test')->isTrue());
}
public function testResolve2(): void
{
$table = $this->createMock(Table::class);
$resolver = new Table\ScopeDataResolver($table);
$table->expects($this->once())
->method('getScopeData')
->willReturnMap([
['Test', ScopeData::fromRaw(false)]
]);
$this->assertTrue($resolver->resolve('Test')->isFalse());
}
public function testResolve3(): void
{
$table = $this->createMock(Table::class);
$resolver = new Table\ScopeDataResolver($table);
$table->expects($this->once())
->method('getScopeData')
->with('Test')
->willReturn(ScopeData::fromRaw((object) ['create' => 'yes', 'edit' => 'no']));
$result = $resolver->resolve('Test');
$this->assertEquals('yes', $result->getCreate());
$this->assertEquals('no', $result->getEdit());
}
public function testResolve4(): void
{
$table = $this->createMock(Table::class);
$resolver = new Table\ScopeDataResolver($table);
$table->expects($this->once())
->method('getScopeData')
->with('Test')
->willReturn(ScopeData::fromRaw((object) ['create' => 'yes', 'edit' => 'no']));
$this->assertTrue($resolver->resolve('boolean:Test')->isTrue());
}
public function testResolve5(): void
{
$table = $this->createMock(Table::class);
$resolver = new Table\ScopeDataResolver($table);
$table->expects($this->once())
->method('getScopeData')
->with('Test')
->willReturn(ScopeData::fromRaw((object) ['create' => 'no', 'edit' => 'no']));
$this->assertTrue($resolver->resolve('boolean:Test')->isTrue());
}
}

View File

@@ -0,0 +1,146 @@
<?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\Acl;
use Espo\Core\Acl\ScopeData;
use Espo\Core\Acl\Table;
use InvalidArgumentException;
use RuntimeException;
class ScopeDataTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
}
public function testBooleanTrue()
{
$data = ScopeData::fromRaw(true);
$this->assertTrue($data->isBoolean());
$this->assertTrue($data->isTrue());
$this->assertFalse($data->isFalse());
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
}
public function testBooleanFalse()
{
$data = ScopeData::fromRaw(false);
$this->assertTrue($data->isBoolean());
$this->assertTrue($data->isFalse());
$this->assertFalse($data->isTrue());
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
}
public function testNotBoolean()
{
$data = ScopeData::fromRaw((object) []);
$this->assertFalse($data->isBoolean());
$this->assertFalse($data->isTrue());
$this->assertFalse($data->isFalse());
}
public function testInvalid()
{
$this->expectException(InvalidArgumentException::class);
ScopeData::fromRaw(null);
}
public function testRecord()
{
$raw = (object) [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_ALL,
Table::ACTION_EDIT => Table::LEVEL_TEAM,
Table::ACTION_DELETE => Table::LEVEL_NO,
];
$data = ScopeData::fromRaw($raw);
$this->assertEquals(Table::LEVEL_YES, $data->getCreate());
$this->assertEquals(Table::LEVEL_ALL, $data->getRead());
$this->assertEquals(Table::LEVEL_TEAM, $data->getEdit());
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
$this->assertTrue($data->hasNotNo());
}
public function testRecordEmpty()
{
$raw = (object) [];
$data = ScopeData::fromRaw($raw);
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
$this->assertFalse($data->hasNotNo());
}
public function testRecordOnlyNo()
{
$raw = (object) [
Table::ACTION_CREATE => Table::LEVEL_NO,
Table::ACTION_READ => Table::LEVEL_NO,
Table::ACTION_EDIT => Table::LEVEL_NO,
Table::ACTION_DELETE => Table::LEVEL_NO,
];
$data = ScopeData::fromRaw($raw);
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
$this->assertFalse($data->hasNotNo());
}
public function testAccessingProperty()
{
$this->expectException(RuntimeException::class);
$data = ScopeData::fromRaw(false);
$data->read;
}
public function testBadKey()
{
$this->expectException(RuntimeException::class);
ScopeData::fromRaw((object) [
Table::ACTION_CREATE => false,
]);
}
}

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\Acl\Table;
use Espo\Core\Acl\Table\DefaultCacheKeyProvider;
use Espo\Entities\User;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
class DefaultCacheKeyProviderTest extends TestCase
{
#[DataProvider('provider')]
public function testKey(string $expected, string $userId, ?array $scopes = null): void
{
$user = $this->createMock(User::class);
$user->expects(self::any())
->method('getId')
->willReturn($userId);
$user->expects(self::any())
->method('getScopes')
->willReturn($scopes);
$provider = new DefaultCacheKeyProvider($user);
$this->assertEquals($expected, $provider->get());
}
public static function provider(): array
{
return [
['acl/01', '01', null],
['acl/01/37f6e5e382faedc68e0769892e78d8d3', '01', ['Admin', 'Global']],
['acl/01/99aa06d3014798d86001c324468d497f', '01', []],
];
}
}

View File

@@ -0,0 +1,115 @@
<?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;
use Espo\Core\Acl\AccessChecker\AccessCheckerFactory;
use Espo\Core\Acl\GlobalRestriction;
use Espo\Core\Acl\Map\MapFactory;
use Espo\Core\Acl\OwnershipChecker\OwnershipCheckerFactory;
use Espo\Core\Acl\OwnerUserFieldProvider;
use Espo\Core\Acl\Permission;
use Espo\Core\Acl\Table;
use Espo\Core\Acl\Table\TableFactory;
use Espo\Core\AclManager;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\Event\EventDispatcher;
use Espo\Entities\User;
use PHPUnit\Framework\TestCase;
class AclManagerTest extends TestCase
{
/** @var AclManager */
private $aclManager;
/** @var TableFactory */
private $tableFactory;
/** @var User */
private $user;
private $table;
protected function setUp(): void
{
$this->user = $this->createMock(User::class);
$this->table = $this->createMock(Table::class);
$accessCheckerFactory = $this->createMock(AccessCheckerFactory::class);
$ownershipCheckerFactory = $this->createMock(OwnershipCheckerFactory::class);
$this->tableFactory = $this->createMock(TableFactory::class);
$mapFactory = $this->createMock(MapFactory::class);
$globalRestriction = $this->createMock(GlobalRestriction::class);
$this->aclManager = new AclManager(
accessCheckerFactory: $accessCheckerFactory,
ownershipCheckerFactory: $ownershipCheckerFactory,
tableFactory: $this->tableFactory,
mapFactory: $mapFactory,
globalRestriction: $globalRestriction,
ownerUserFieldProvider: $this->createMock(OwnerUserFieldProvider::class),
entityManager: $this->createMock(EntityManager::class),
eventDispatcher: $this->createMock(EventDispatcher::class),
);
}
private function initTableFactory(User $user, Table $table): void
{
$this->tableFactory
->expects($this->once())
->method('create')
->with($user)
->willReturn($table);
}
public function testGetPermissionLevel1(): void
{
$this->initTableFactory($this->user, $this->table);
$this->table
->expects($this->once())
->method('getPermissionLevel')
->with(Permission::ASSIGNMENT)
->willReturn(Table::LEVEL_YES);
$this->aclManager->getPermissionLevel($this->user, Permission::ASSIGNMENT);
}
public function testGetPermissionLevel2(): void
{
$this->initTableFactory($this->user, $this->table);
$this->table
->expects($this->once())
->method('getPermissionLevel')
->with(Permission::ASSIGNMENT)
->willReturn(Table::LEVEL_YES);
$this->aclManager->getPermissionLevel($this->user, Permission::ASSIGNMENT);
}
}

View File

@@ -0,0 +1,81 @@
<?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\Api;
use Espo\Core\Api\ControllerActionProcessor;
use Espo\Core\Api\RequestWrapper;
use Espo\Core\Api\ResponseWrapper;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\ClassFinder;
use PHPUnit\Framework\TestCase;
use tests\unit\testClasses\Controllers\TestController;
class ActionProcessorTest extends TestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testAction1()
{
$classFinder = $this->createMock(ClassFinder::class);
$injectableFactory = $this->createMock(InjectableFactory::class);
$request = $this->createMock(RequestWrapper::class);
$response = $this->createMock(ResponseWrapper::class);
$actionProcessor = new ControllerActionProcessor($injectableFactory, $classFinder);
$controller = $this->getMockBuilder(TestController::class)->disableOriginalConstructor()->getMock();
$classFinder
->expects($this->once())
->method('find')
->with('Controllers', 'Test')
->willReturn(TestController::class);
$request
->expects($this->once())
->method('getMethod')
->willReturn('POST');
$injectableFactory
->expects($this->once())
->method('createWith')
->with(TestController::class, ['name' => 'Test'])
->willReturn($controller);
$controller
->expects($this->once())
->method('postActionHello')
->with($request, $response);
$actionProcessor->process('Test', 'hello', $request, $response);
}
}

View File

@@ -0,0 +1,272 @@
<?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\Api;
use Espo\Core\Api\Auth;
use Espo\Core\Api\Method;
use Espo\Core\Api\RequestWrapper;
use Espo\Core\Api\Response;
use Espo\Core\Api\Route\ContentType;
use Espo\Core\Authentication\Authentication;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Authentication\HeaderKey;
use Espo\Core\Authentication\Login\MethodResolver;
use Espo\Core\Authentication\Result;
use Espo\Core\Authentication\Result\Data;
use Espo\Core\Utils\Json;
use Espo\Core\Utils\Log;
use Espo\Entities\User;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
use Slim\Psr7\Factory\RequestFactory;
use Slim\Psr7\Factory\StreamFactory;
class AuthTest extends TestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testProcessNoAuth(): void
{
$request = $this->createRequestInstance();
$response = $this->createMock(Response::class);
$authentication = $this->createMock(Authentication::class);
$authentication
->expects(self::once())
->method('login')
->willReturn(
Result::secondStepRequired($this->createMock(User::class), Data::create())
);
$auth = new Auth(
log: $this->createMock(Log::class),
authentication: $authentication,
methodResolver: $this->createMock(MethodResolver::class),
authRequired: false,
);
$result = $auth->process($request, $response);
$this->assertTrue($result->isResolvedUseNoAuth());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testProcessAuthFail(): void
{
$request = $this->createRequestInstance();
$response = $this->createMock(Response::class);
$authentication = $this->createMock(Authentication::class);
$authentication
->expects(self::once())
->method('login')
->willReturn(
Result::fail()
);
$auth = new Auth(
log: $this->createMock(Log::class),
authentication: $authentication,
methodResolver: $this->createMock(MethodResolver::class),
);
$result = $auth->process($request, $response);
$this->assertFalse($result->isResolved());
$this->assertFalse($result->isResolvedUseNoAuth());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testCgiHeader(): void
{
$request = $this->createRequest(
method: Method::GET,
headers: [
'Http-Espo-Cgi-Auth' => 'Basic ' . base64_encode('test:1'),
],
);
$response = $this->createMock(Response::class);
$authentication = $this->createMock(Authentication::class);
$authentication
->expects(self::once())
->method('login')
->with(
AuthenticationData::create()
->withUsername('test')
->withPassword('1')
)
->willReturn(
Result::success($this->createMock(User::class))
);
$auth = new Auth(
log: $this->createMock(Log::class),
authentication: $authentication,
methodResolver: $this->createMock(MethodResolver::class),
authRequired: true,
);
$result = $auth->process($request, $response);
$this->assertTrue($result->isResolved());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testCookie(): void
{
$request = $this->createRequest(
method: Method::GET,
cookieParams: [
'auth-token' => '001',
],
);
$response = $this->createMock(Response::class);
$authentication = $this->createMock(Authentication::class);
$authentication
->expects(self::once())
->method('login')
->with(
AuthenticationData::create()
->withPassword('001')
->withByTokenOnly(true)
)
->willReturn(
Result::success($this->createMock(User::class))
);
$auth = new Auth(
log: $this->createMock(Log::class),
authentication: $authentication,
methodResolver: $this->createMock(MethodResolver::class),
authRequired: true,
);
$result = $auth->process($request, $response);
$this->assertTrue($result->isResolved());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testIgnoreCookie(): void
{
$request = $this->createRequest(
method: Method::POST,
headers: [
'Sec-Fetch-Mode' => 'navigate',
'Content-Type' => ContentType::MULTIPART_FORM_DATA . '; charset=utf-8'
],
cookieParams: [
'auth-token' => '001',
]
);
$response = $this->createMock(Response::class);
$authentication = $this->createMock(Authentication::class);
$authentication
->expects(self::never())
->method('login');
$auth = new Auth(
log: $this->createMock(Log::class),
authentication: $authentication,
methodResolver: $this->createMock(MethodResolver::class),
authRequired: true,
);
$result = $auth->process($request, $response);
$this->assertFalse($result->isResolved());
}
/**
* @param array<string, string> $headers
* @param array<string, string> $queryParams
* @param array<string, string> $cookieParams
*/
public static function createRequest(
string $method,
array $queryParams = [],
array $headers = [],
?string $body = null,
array $cookieParams = [],
): RequestWrapper {
$request = (new RequestFactory())
->createRequest($method, 'http://localhost/?' . http_build_query($queryParams));
if (!$request instanceof ServerRequestInterface) {
throw new RuntimeException();
}
foreach ($headers as $name => $value) {
$request = $request->withHeader($name, $value);
}
$request = $request->withCookieParams($cookieParams);
if ($body) {
$request = $request->withBody(
(new StreamFactory)->createStream($body)
);
}
return new RequestWrapper($request, '', []);
}
private function createRequestInstance(): RequestWrapper
{
return $this->createRequest(
method: 'POST',
headers: [
'Content-Type' => 'application/json',
HeaderKey::AUTHORIZATION => base64_encode('test:1'),
],
body: Json::encode((object) []),
);
}
}

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\Api;
use Espo\Core\Api\ConfigDataProvider;
use Espo\Core\Api\ErrorOutput;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Utils\Log;
use PHPUnit\Framework\TestCase;
use RuntimeException;
class ErrorOutputTest extends TestCase
{
public function testExceptionNotExposed(): void
{
$e = new RuntimeException();
$errorOutput = $this->createErrorOutput(exposeExceptions: false);
$request = $this->createMock(Request::class);
$response = $this->createMock(Response::class);
$response->expects(self::never())
->method('setHeader')
->with(ErrorOutput::HEADER_STATUS_REASON);
$errorOutput->process($request, $response, $e);
}
public function testExceptionExposed(): void
{
$e = new RuntimeException("Test.");
$errorOutput = $this->createErrorOutput(exposeExceptions: true);
$request = $this->createMock(Request::class);
$response = $this->createMock(Response::class);
$response->expects(self::once())
->method('setHeader')
->with(ErrorOutput::HEADER_STATUS_REASON);
$errorOutput->process($request, $response, $e);
}
/**
* @return ConfigDataProvider
*/
private function createConfig(bool $exposeExceptions): ConfigDataProvider
{
$config = $this->createMock(ConfigDataProvider::class);
$config
->expects(self::any())
->method('exposeExceptions')
->willReturn($exposeExceptions);
return $config;
}
private function createErrorOutput(bool $exposeExceptions): ErrorOutput
{
return new ErrorOutput(
log: $this->createMock(Log::class),
config: $this->createConfig(exposeExceptions: $exposeExceptions),
);
}
}

View File

@@ -0,0 +1,229 @@
<?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\Api;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface as Psr7Request;
use Psr\Http\Message\StreamInterface;
use Slim\Psr7\Factory\RequestFactory;
use Espo\Core\Api\RequestWrapper;
class RequestTest extends TestCase
{
private $request;
protected function setUp(): void
{
$this->request = $this->getMockBuilder(Psr7Request::class)->disableOriginalConstructor()->getMock();
}
protected function createRequest(array $queryParams, array $routeParams = []) : RequestWrapper
{
$this->request
->expects($this->any())
->method('getQueryParams')
->willReturn($queryParams);
return new RequestWrapper($this->request, '', $routeParams);
}
public function testHasQueryParam()
{
$request = $this->createRequest([
'id' => '1',
]);
$this->assertTrue($request->hasQueryParam('id'));
$this->assertFalse($request->hasQueryParam('test'));
}
public function testHasRouteParam()
{
$request = $this->createRequest(
[
],
[
'id' => '1',
]
);
$this->assertTrue($request->hasRouteParam('id'));
$this->assertFalse($request->hasRouteParam('test'));
}
public function testGetQueryParam()
{
$request = $this->createRequest([
'id' => '1',
]);
$this->assertEquals('1', $request->getQueryParam('id'));
}
public function testGetRouteParam()
{
$request = $this->createRequest(
[
],
[
'id' => '1',
]
);
$this->assertEquals('1', $request->getRouteParam('id'));
}
public function testGet()
{
$request = $this->createRequest(
[
'id' => '1',
],
[
'id' => '2',
]
);
$this->assertEquals('2', $request->get('id'));
}
protected function createRequestWithBody(string $contents) : RequestWrapper
{
$body = $this->createMock(StreamInterface::class);
$body
->expects($this->any())
->method('getContents')
->willReturn($contents);
$this->request
->expects($this->any())
->method('getBody')
->willReturn($body);
$this->request
->expects($this->any())
->method('hasHeader')
->with('Content-Type')
->willReturn(true);
$this->request
->expects($this->any())
->method('getHeader')
->with('Content-Type')
->willReturn(['application/json']);
return new RequestWrapper($this->request);
}
public function testGetParsedBody()
{
$original = (object) [
'key1' => '1',
'key2' => (object) [
'key21' => [
'211',
'212',
(object) [
'2111' => '1',
],
],
],
'key3' => [
'31',
'32',
null,
],
'key4' => null,
];
$contents = json_encode($original);
$request = $this->createRequestWithBody($contents);
$parsed = $request->getParsedBody();
$anotherParsed = $request->getParsedBody();
$this->assertEquals($parsed, $original);
$this->assertEquals($parsed, $anotherParsed);
$this->assertNotSame($parsed, $anotherParsed);
$this->assertNotSame($parsed->key2, $anotherParsed->key2);
$this->assertNotSame($parsed->key2->key21[2], $anotherParsed->key2->key21[2]);
}
public function testContentType1(): void
{
$request = (new RequestFactory())
->createRequest('POST', 'http://localhost/?')
->withHeader('Content-Type', 'application/json; charset=utf-8');
$requestWrapped = new RequestWrapper($request);
$this->assertEquals('application/json', $requestWrapped->getContentType());
}
public function testContentType2(): void
{
$request = (new RequestFactory())
->createRequest('POST', 'http://localhost/?')
->withHeader('Content-Type', 'application/json');
$requestWrapped = new RequestWrapper($request);
$this->assertEquals('application/json', $requestWrapped->getContentType());
}
public function testContentTypeEmpty(): void
{
$request = (new RequestFactory())
->createRequest('POST', 'http://localhost/?');
$requestWrapped = new RequestWrapper($request);
$this->assertEquals(null, $requestWrapped->getContentType());
}
public function testHeaderAsArray(): void
{
$request = (new RequestFactory())
->createRequest('POST', 'http://localhost/?')
->withAddedHeader('Test', '1')
->withAddedHeader('Test', '2');
$requestWrapped = new RequestWrapper($request);
$this->assertEquals(['1', '2'], $requestWrapped->getHeaderAsArray('Test'));
}
}

View File

@@ -0,0 +1,61 @@
<?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\Api;
use Slim\Psr7\Factory\ResponseFactory;
use Espo\Core\Api\ResponseWrapper;
class ResponseTest extends \PHPUnit\Framework\TestCase
{
public function testHeaderAsArray(): void
{
$response = (new ResponseFactory())
->createResponse()
->withAddedHeader('Test', '1')
->withAddedHeader('Test', '2');
$wrapped = new ResponseWrapper($response);
$this->assertEquals(['1', '2'], $wrapped->getHeaderAsArray('Test'));
}
public function testGetHeaderNames(): void
{
$response = (new ResponseFactory())
->createResponse()
->withAddedHeader('Test', '1')
->withAddedHeader('Test', '2')
->withAddedHeader('Content-Type', 'application/json');
$wrapped = new ResponseWrapper($response);
$this->assertEquals(['Test', 'Content-Type'], $wrapped->getHeaderNames());
}
}

View File

@@ -0,0 +1,109 @@
<?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\Api;
use Espo\Core\Api\AuthBuilderFactory;
use Espo\Core\Api\ControllerActionProcessor;
use Espo\Core\Api\ErrorOutput;
use Espo\Core\Api\Method;
use Espo\Core\Api\MiddlewareProvider;
use Espo\Core\Api\ProcessData;
use Espo\Core\Api\Route;
use Espo\Core\Api\Route\ContentType;
use Espo\Core\Api\RouteProcessor;
use Espo\Core\ApplicationUser;
use Espo\Core\Authentication\AuthenticationFactory;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Log;
use PHPUnit\Framework\TestCase;
use Slim\Psr7\Factory\ResponseFactory;
class RouteProcessorTest extends TestCase
{
public function testConsumesFail(): void
{
$request = AuthTest::createRequest(
method: Method::POST,
headers: [
'Content-Type' => ContentType::TEXT_PLAIN,
],
);
$route = new Route(
method: Method::POST,
route: '/test',
adjustedRoute: '/test',
params: [],
noAuth: false,
consumes: [
ContentType::APPLICATION_JSON,
],
actionClassName: null,
);
$errorOutput = $this->createMock(ErrorOutput::class);
$processor = new RouteProcessor(
authenticationFactory: $this->createMock(AuthenticationFactory::class),
authBuilderFactory: $this->createMock(AuthBuilderFactory::class),
errorOutput: $errorOutput,
config: $this->createMock(Config::class),
log: $this->createMock(Log::class),
applicationUser: $this->createMock(ApplicationUser::class),
actionProcessor: $this->createMock(ControllerActionProcessor::class),
middlewareProvider: $this->createMock(MiddlewareProvider::class),
injectableFactory: $this->createMock(InjectableFactory::class),
);
$response = (new ResponseFactory())->createResponse();
$errorOutput->expects(self::once())
->method('process')
->with(
self::anything(),
self::anything(),
$this->callback(function ($e) {
return $e instanceof BadRequest;
})
);
$processor->process(
processData: new ProcessData(
route: $route,
basePath: '',
routeParams: [],
),
request: $request->toPsr7(),
response: $response,
);
}
}

View File

@@ -0,0 +1,53 @@
<?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\Authentication\AuthToken;
use Espo\Core\Authentication\AuthToken\Data;
use PHPUnit\Framework\TestCase;
class AuthTokenDataTest extends TestCase
{
public function testCreate()
{
$authTokenData = Data::create([
'passwordVersion' => 1,
'ipAddress' => 'ip-address',
'userId' => 'user-id',
'portalId' => 'portal-id',
'createSecret' => true,
]);
$this->assertEquals(1, $authTokenData->getPasswordVersion());
$this->assertEquals('ip-address', $authTokenData->getIpAddress());
$this->assertEquals('user-id', $authTokenData->getUserId());
$this->assertEquals('portal-id', $authTokenData->getPortalId());
$this->assertTrue($authTokenData->toCreateSecret());
}
}

View File

@@ -0,0 +1,59 @@
<?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\Authentication;
use Espo\Core\Authentication\AuthenticationData;
class AuthenticationDataTest extends \PHPUnit\Framework\TestCase
{
public function testCreate1(): void
{
$data = AuthenticationData::create()
->withUsername('u')
->withPassword('p')
->withMethod(null);
$this->assertEquals('u', $data->getUsername());
$this->assertEquals('p', $data->getPassword());
$this->assertNull($data->getMethod());
}
public function testCreate2(): void
{
$data = AuthenticationData::create()
->withUsername(null)
->withPassword(null)
->withMethod('m');
$this->assertNull($data->getUsername());
$this->assertNull($data->getPassword());
$this->assertEquals('m', $data->getMethod());
}
}

View File

@@ -0,0 +1,133 @@
<?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\Authentication\Jwt;
use Espo\Core\Authentication\Jwt\DefaultKeyFactory;
use Espo\Core\Authentication\Jwt\Key;
use Espo\Core\Authentication\Jwt\SignatureVerifiers\Hmac;
use Espo\Core\Authentication\Jwt\SignatureVerifiers\Rsa;
use Espo\Core\Authentication\Jwt\Token;
use phpseclib3\Common\Functions\Strings;
use PHPUnit\Framework\TestCase;
class TokenTest extends TestCase
{
public function testToken1(): void
{
$raw = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibm" .
"FtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.cThIIoDvwdueQB468K5xDc5633seEFoqwxjF_xSJyQQ";
$token = Token::create($raw);
$this->assertEquals('HS256', $token->getHeader()->getAlg());
$this->assertEquals('1234567890', $token->getPayload()->getSub());
$this->assertEquals('John Doe', $token->getPayload()->get('name'));
}
public function testToken2(): void
{
$raw = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0Ij" .
"oxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzgwMjIsIm5iZiI6MTUxNjIzODAyMywiYXV0aF90aW1lIjoiMTUxNjIzODAyNCJ9." .
"7Z45aUyLR9o8lUaIvxkO7SzOhTaXgfXf_rEFYnvTPL8";
$token = Token::create($raw);
$this->assertEquals(1516238022, $token->getPayload()->getExp());
$this->assertEquals(1516239022, $token->getPayload()->getIat());
$this->assertEquals(1516238023, $token->getPayload()->getNbf());
$this->assertEquals(1516238024, $token->getPayload()->getAuthTime());
}
public function testVerifySignatureHS256(): void
{
$raw = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." .
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0" .
"IjoxNTE2MjM5MDIyfQ.S2ZL7D-D3VeduQ44Cy2qLRFxHV43gRGSZtlfJ2MJ57g";
$token = Token::create($raw);
$this->assertEquals('HS256', $token->getHeader()->getAlg());
$this->assertEquals('1234567890', $token->getPayload()->getSub());
$verifier1 = new Hmac('HS256', '123456789');
$this->assertTrue($verifier1->verify($token));
$verifier2 = new Hmac('HS256', '0000000000');
$this->assertFalse($verifier2->verify($token));
$verifier3 = new Hmac('HS512', '123456789');
$this->assertFalse($verifier3->verify($token));
}
public function testVerifySignatureRS256(): void
{
$raw =
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjAwMSJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9l" .
"IiwiaWF0IjoxNTE2MjM5MDIyfQ.ZIOKC5KV6E1omC_KnQHgG5h9Z8G3g8jc1uUiI0RkQITAM-oS_3qivauy5jJnMX_N9-Bz2ZZSQ6" .
"4AA_LbELCSoLnQ4HJBRzK8XfNeTVwhebjelUc8b_qcWC16lUWydd1GFYQQYbPfis_EN0UNM5TvfPpZ24YPujxJqZ0vrOOM-T6U73PtQ" .
"TLfErmdO8cd_drHc75lSmWvXjpRl7zAj9vGhO_nJRh3-tPGprkFMvC4FLWOF5L_4eS3bJhhj7GUxyYYNi2ATbO7SRCPUp2Ck_aiJNlbS" .
"Vdhbt_2Ls8VGnSPDdPf9UDUiXqYadueqmyrRucbBNHYn46cehnuONJa3gMaSA";
$token = Token::create($raw);
$pem =
"-----BEGIN PUBLIC KEY-----\r\n" .
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\r\n" .
"4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\r\n" .
"+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\r\n" .
"kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\r\n" .
"0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\r\n" .
"cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\r\n" .
"mwIDAQAB\r\n" .
"-----END PUBLIC KEY-----\r\n";
$pk = openssl_pkey_get_public($pem);
$keyData = openssl_pkey_get_details($pk);
$rawKey = (object) [
'kid' => '001',
'alg' => 'RS256',
'kty' => 'RSA',
'n' => Strings::base64url_encode($keyData['rsa']['n']),
'e' => Strings::base64url_encode($keyData['rsa']['e']),
];
$key = (new DefaultKeyFactory())->create($rawKey);
$verifier1 = new Rsa('RS256', [$key]);
$this->assertTrue($verifier1->verify($token));
$verifier1 = new Rsa('RS256', [$this->createMock(Key::class), $key]);
$this->assertTrue($verifier1->verify($token));
$verifier1 = new Rsa('RS512', [$key]);
$this->assertFalse($verifier1->verify($token));
}
}

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\Authentication\Login;
use Espo\Core\Api\Method;
use Espo\Core\Api\RequestWrapper;
use Espo\Core\Authentication\ConfigDataProvider;
use Espo\Core\Authentication\Login\MetadataParams;
use Espo\Core\Authentication\Login\MethodResolver;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Slim\Psr7\Factory\ServerRequestFactory;
class MethodResolverTest extends TestCase
{
/**
* @param MetadataParams[] $paramsList
* @param array{string, string} $headerPair
*/
#[DataProvider('provider')]
public function testResolve(array $paramsList, array $headerPair, ?string $expected): void
{
$this->processTest($paramsList, $headerPair, $expected);
}
public static function provider(): array
{
$paramsList1 = [
new MetadataParams(
method: 'Test',
),
new MetadataParams(
method: 'ApiKey',
api: true,
credentialsHeader: 'X-Api-Key',
),
new MetadataParams(
method: 'OAuth',
api: true,
credentialsHeader: 'Authorization',
credentialsHeaderScheme: 'Bearer',
),
];
$paramsList2 = [
new MetadataParams(
method: 'Some',
api: true,
credentialsHeader: 'Authorization',
),
new MetadataParams(
method: 'OAuth',
api: true,
credentialsHeader: 'Authorization',
credentialsHeaderScheme: 'Bearer',
),
];
$paramsList3 = [
new MetadataParams(
method: 'OAuth',
api: true,
credentialsHeader: 'Authorization',
credentialsHeaderScheme: 'Bearer',
),
new MetadataParams(
method: 'Some',
api: true,
credentialsHeader: 'Authorization',
),
];
return [
[
$paramsList1,
['Authorization', 'Bearer test'],
'OAuth',
],
[
$paramsList1,
['X-Api-Key', 'test'],
'ApiKey',
],
[
$paramsList1,
['Espo-Authorization', 'test'],
null,
],
[
$paramsList2,
['Authorization', 'Bearer test'],
'OAuth',
],
[
$paramsList3,
['Authorization', 'Bearer test'],
'OAuth',
],
[
$paramsList2,
['Authorization', 'test'],
'Some',
],
[
$paramsList3,
['Authorization', 'test'],
'Some',
],
];
}
/**
* @param MetadataParams[] $paramsList
* @param array{string, string} $headerPair
*/
private function processTest(array $paramsList, array $headerPair, ?string $expected): void
{
$config = $this->createMock(ConfigDataProvider::class);
$config
->expects(self::any())
->method('getLoginMetadataParamsList')
->willReturn($paramsList);
$request = new RequestWrapper(
psr7Request: (new ServerRequestFactory())->createServerRequest(Method::GET, 'http://localhost')
->withHeader($headerPair[0], $headerPair[1]),
);
$resolver = new MethodResolver($config);
$this->assertEquals($expected, $resolver->resolve($request));
}
}

View File

@@ -0,0 +1,96 @@
<?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\Authentication\Logins\Oidc;
use Espo\Core\Acl\Cache\Clearer;
use Espo\Core\ApplicationState;
use Espo\Core\Authentication\Oidc\ConfigDataProvider;
use Espo\Core\Authentication\Oidc\UserProvider\DefaultUserInfoPopulator;
use Espo\Core\Authentication\Oidc\UserProvider\Sync;
use Espo\Core\Authentication\Oidc\UserProvider\UsernameValidator;
use Espo\Core\Authentication\Oidc\UserProvider\UserRepository;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\PasswordHash;
use PHPUnit\Framework\TestCase;
class SyncTest extends TestCase
{
private ?Sync $sync = null;
private ?Config $config = null;
protected function setUp(): void
{
$this->config = $this->createMock(Config::class);
$configDataProvider = $this->createMock(ConfigDataProvider::class);
$populator = new DefaultUserInfoPopulator();
$this->sync = new Sync(
$this->createMock(UsernameValidator::class),
$this->config,
$configDataProvider,
$this->createMock(UserRepository::class),
$this->createMock(PasswordHash::class),
$this->createMock(Clearer::class),
$this->createMock(ApplicationState::class),
$populator,
);
}
public function testNormalizeUsername(): void
{
$this->config
->expects($this->any())
->method('get')
->with('userNameRegularExpression')
->willReturn('[^a-z0-9\-@_\.\s]');
$this->assertEquals(
'test_name',
$this->sync->normalizeUsername('test_name')
);
$this->assertEquals(
'test_name',
$this->sync->normalizeUsername('test|name')
);
$this->assertEquals(
'test@name',
$this->sync->normalizeUsername('test@name')
);
$this->assertEquals(
'test_name',
$this->sync->normalizeUsername('test name')
);
}
}

View File

@@ -0,0 +1,53 @@
<?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\Authentication;
use Espo\Core\Authentication\Result\Data as ResultData;
class ResultDataTest extends \PHPUnit\Framework\TestCase
{
public function testCreate1(): void
{
$data = ResultData::create()
->withMessage('hello')
->withDataItem('test1', '1')
->withDataItem('test2', '2');
$this->assertEquals(
(object) [
'test1' => '1',
'test2' => '2',
],
$data->getData()
);
$this->assertEquals('hello', $data->getMessage());
}
}

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\Authentication\Util;
use PHPUnit\Framework\TestCase;
class IpAddressUtilTest extends TestCase
{
public function testWhitelist(): void
{
$util = new \Espo\Core\Authentication\Util\IpAddressUtil();
$this->assertTrue(
$util->isInWhitelist('192.168.0.1', [
'192.168.0.1',
])
);
$this->assertFalse(
$util->isInWhitelist('192.168.0.1', [
'192.168.0.0',
])
);
$this->assertTrue(
$util->isInWhitelist('192.168.0.1', [
'192.168.0.1',
'192.168.0.1',
])
);
$this->assertTrue(
$util->isInWhitelist('192.168.0.5', [
'192.168.0.1',
'192.168.0.1/24',
])
);
$this->assertFalse(
$util->isInWhitelist('0.0.0.5', [
'192.168.0.1/24',
])
);
}
}

View File

@@ -0,0 +1,748 @@
<?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\Binding;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\Binding;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingContainerBuilder;
use Espo\Core\Binding\BindingData;
use Espo\Core\Binding\BindingLoader;
use Espo\Core\Binding\ContextualBinder;
use Espo\Core\Binding\Key\NamedClassKey;
use Espo\Core\Binding\Key\NamedKey;
use Espo\Core\Binding\Key\QualifiedClassKey;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionParameter;
use ReflectionNamedType;
use tests\unit\testClasses\Core\Binding\Class0;
use tests\unit\testClasses\Core\Binding\Class1;
use tests\unit\testClasses\Core\Binding\SomeClass1Alt;
use tests\unit\testClasses\Core\Binding\SomeClass2Alt;
use tests\unit\testClasses\Core\Binding\SomeInterface1;
use tests\unit\testClasses\Core\Binding\SomeInterface2;
use tests\unit\testClasses\Core\Binding\SomeClass1;
use tests\unit\testClasses\Core\Binding\SomeClass2;
class BindingContainerTest extends TestCase
{
private ?Binder $binder = null;
private ?BindingLoader $loader = null;
protected function setUp(): void
{
$this->loader = $this->createMock(BindingLoader::class);
$data = new BindingData();
$this->binder = new Binder($data);
$this->loader
->expects($this->any())
->method('load')
->willReturn($data);
}
private function createClassMock(string $className): ReflectionClass
{
$class = $this->createMock(ReflectionClass::class);
$class
->expects($this->any())
->method('getName')
->willReturn($className);
return $class;
}
private function createParamMock(string $name, ?string $className = null, ?string $qualifier = null): ReflectionParameter
{
$param = $this->createMock(ReflectionParameter::class);
$class = null;
$type = $this->createMock(ReflectionNamedType::class);
if ($className) {
$class = $this->createClassMock($className);
$type
->expects($this->any())
->method('isBuiltin')
->willReturn(false);
$type
->expects($this->any())
->method('getName')
->willReturn($className);
}
$type
->expects($this->any())
->method('isBuiltin')
->willReturn(true);
$param
->expects($this->any())
->method('getType')
->willReturn($type);
$param
->expects($this->any())
->method('getName')
->willReturn($name);
$param
->expects($this->any())
->method('getClass')
->willReturn($class);
if ($qualifier) {
$attribute = $this->createMock(ReflectionClass::class);
$param
->method('getAttributes')
->willReturn([$attribute]);
$attribute->method('newInstance')
->willReturn((object) ['qualifier' => $qualifier]);
}
return $param;
}
protected function createContainer(): BindingContainer
{
return new BindingContainer($this->loader->load());
}
public function testHasTrue(): void
{
$this->binder->bindService('Espo\\Test', 'test');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$this->assertTrue(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testHasNoContextTrue(): void
{
$this->binder->bindService('Espo\\Test', 'test');
$param = $this->createParamMock('test', 'Espo\\Test');
$this->assertTrue(
$this->createContainer()->hasByParam(null, $param)
);
}
public function testHasFalse(): void
{
$this->binder->bindService('Espo\\Test', 'test');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Hello');
$this->assertFalse(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testHasNoContextFalse(): void
{
$this->binder->bindService('Espo\\Test', 'test');
$param = $this->createParamMock('test', 'Espo\\Hello');
$this->assertFalse(
$this->createContainer()->hasByParam(null, $param)
);
}
public function testHasContextTrue0(): void
{
$this->binder
->inContext('Espo\\Context', function (ContextualBinder $binder): void {
$binder->bindService('Espo\\Test', 'test');
});
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$this->assertTrue(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testHasContextTrue1(): void
{
$this->binder
->for('Espo\\Context')
->bindService('Espo\\Test', 'test');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$this->assertTrue(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testHasContextTrue2(): void
{
$this->binder
->for('Espo\\Context')
->bindValue('$test', 'Test Value');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test');
$this->assertTrue(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testHasContextTrue3(): void
{
$this->binder
->for('Espo\\Context')
->bindValue(NamedKey::create('test'), 'Test Value');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test');
$this->assertTrue(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testHasContextTrue4(): void
{
$this->binder
->for('Espo\\Context')
->bindService(NamedClassKey::create('Espo\\Test', 'test'), 'service');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$this->assertTrue(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testHasContextFalse1(): void
{
$this->binder
->for('Espo\\Context')
->bindService('Espo\\Test', 'test');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Hello');
$this->assertFalse(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testHasContextFalse2(): void
{
$this->binder
->for('Espo\\Context')
->bindService('Espo\\Test', 'test');
$class = $this->createClassMock('Espo\\ContextOther');
$param = $this->createParamMock('test', 'Espo\\Test');
$this->assertFalse(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testHasContextFalse3(): void
{
$this->binder
->for('Espo\\Context')
->bindValue('$test', 'Test Value');
$class = $this->createClassMock('Espo\\Other');
$param = $this->createParamMock('test');
$this->assertFalse(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testGetClassNameImplementation(): void
{
$this->binder->bindImplementation('Espo\\Test', 'Espo\\ImplTest');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::IMPLEMENTATION_CLASS_NAME, $binding->getType());
$this->assertEquals('Espo\\ImplTest', $binding->getValue());
}
public function testGetClassNameFactory(): void
{
$this->binder->bindFactory('Espo\\Test', 'Espo\\TestFactory');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::FACTORY_CLASS_NAME, $binding->getType());
$this->assertEquals('Espo\\TestFactory', $binding->getValue());
}
public function testGetService(): void
{
$this->binder->bindService('Espo\\Test', 'test');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CONTAINER_SERVICE, $binding->getType());
$this->assertEquals('test', $binding->getValue());
}
public function testGetCallback(): void
{
$this->binder->bindCallback(
'Espo\\Test',
function () {
return 'test';
}
);
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CALLBACK, $binding->getType());
$this->assertIsCallable($binding->getValue());
}
public function testBindInstance(): void
{
$className = 'Espo\\Core\\Application';
$instance = $this->createMock($className);
$this->binder->bindInstance($className, $instance);
$param = $this->createParamMock('test', $className);
$binding = $this->createContainer()->getByParam(null, $param);
$this->assertEquals(Binding::VALUE, $binding->getType());
$this->assertSame($instance, $binding->getValue());
}
public function testContextBindInstance(): void
{
$className = 'Espo\\Core\\Application';
$instance = $this->createMock($className);
$this->binder
->for('Espo\\Context')
->bindInstance($className, $instance);
$param = $this->createParamMock('test', $className);
$class = $this->createClassMock('Espo\\Context');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::VALUE, $binding->getType());
$this->assertSame($instance, $binding->getValue());
}
public function testContextGetCallback(): void
{
$this->binder
->for('Espo\\Context')
->bindCallback(
'Espo\\Test',
function () {
return 'test';
}
);
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CALLBACK, $binding->getType());
$this->assertIsCallable($binding->getValue());
}
public function testRebindGlobal(): void
{
$this->binder->bindService('Espo\\Test', 'test');
$this->binder->bindService('Espo\\Test', 'testHello');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CONTAINER_SERVICE, $binding->getType());
$this->assertEquals('testHello', $binding->getValue());
}
public function testBindInterfaceWithParamNameGlobal(): void
{
$this->binder->bindService('Espo\\Test $name', 'testName');
$this->binder->bindService('Espo\\Test', 'test');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('name', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CONTAINER_SERVICE, $binding->getType());
$this->assertEquals('testName', $binding->getValue());
$param = $this->createParamMock('hello', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CONTAINER_SERVICE, $binding->getType());
$this->assertEquals('test', $binding->getValue());
}
public function testContextGetClassNameImplementation(): void
{
$this->binder
->for('Espo\\Context')
->bindImplementation('Espo\\Test', 'Espo\\ImplTest');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::IMPLEMENTATION_CLASS_NAME, $binding->getType());
$this->assertEquals('Espo\\ImplTest', $binding->getValue());
}
public function testContextGetClassNameFactory(): void
{
$this->binder
->for('Espo\\Context')
->bindFactory('Espo\\Test', 'Espo\\TestFactory');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::FACTORY_CLASS_NAME, $binding->getType());
$this->assertEquals('Espo\\TestFactory', $binding->getValue());
}
public function testNoContextClassName(): void
{
$this->binder
->for('Espo\\Context')
->bindImplementation('Espo\\Test', 'Espo\\ImplTest');
$class = $this->createClassMock('Espo\\AnotherContext');
$param = $this->createParamMock('test', 'Espo\\Test');
$this->assertFalse(
$this->createContainer()->hasByParam($class, $param)
);
}
public function testBindContextInterfaceWithParamNameGlobal(): void
{
$this->binder
->for('Espo\\Context')
->bindService('Espo\\Test $name', 'testName');
$this->binder->bindService('Espo\\Test', 'test');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('name', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CONTAINER_SERVICE, $binding->getType());
$this->assertEquals('testName', $binding->getValue());
$param = $this->createParamMock('hello', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CONTAINER_SERVICE, $binding->getType());
$this->assertEquals('test', $binding->getValue());
}
public function testGetContextParamValue(): void
{
$this->binder
->for('Espo\\Context')
->bindValue('$test', 'Test Value');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::VALUE, $binding->getType());
$this->assertEquals('Test Value', $binding->getValue());
}
public function testGetContextInterfaceValue1(): void
{
$instance = (object) [];
$this->binder
->for('Espo\\Context')
->bindValue('Espo\\SomeClass $test', $instance);
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\SomeClass');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::VALUE, $binding->getType());
$this->assertEquals($instance, $binding->getValue());
}
public function testGetContextInterfaceValue2(): void
{
$instance = (object) [];
$this->binder
->for('Espo\\Context')
->bindValue(NamedClassKey::create('Espo\\SomeClass', 'test'), $instance);
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\SomeClass');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::VALUE, $binding->getType());
$this->assertEquals($instance, $binding->getValue());
}
public function testGetContextService(): void
{
$this->binder
->for('Espo\\Context')
->bindService('Espo\\Test', 'test');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CONTAINER_SERVICE, $binding->getType());
$this->assertEquals('test', $binding->getValue());
}
public function testRebindContextService(): void
{
$this->binder
->for('Espo\\Context')
->bindService('Espo\\Test', 'test');
$this->binder
->for('Espo\\Context')
->bindService('Espo\\Test', 'testHello');
$class = $this->createClassMock('Espo\\Context');
$param = $this->createParamMock('test', 'Espo\\Test');
$binding = $this->createContainer()->getByParam($class, $param);
$this->assertEquals(Binding::CONTAINER_SERVICE, $binding->getType());
$this->assertEquals('testHello', $binding->getValue());
}
public function testBindingContainerBuilder1(): void
{
$container = BindingContainerBuilder::create()
->bindImplementation(SomeInterface1::class, SomeClass1::class)
->inContext(SomeClass1::class, function (ContextualBinder $binder): void {
$binder->bindImplementation(SomeInterface2::class, SomeClass2::class);
})
->build();
$param1 = $this->createParamMock('test', SomeInterface1::class);
$this->assertTrue($container->hasByParam(null, $param1));
$param2 = $this->createParamMock('test', SomeInterface2::class);
$this->assertFalse($container->hasByParam(null, $param2));
$class3 = $this->createClassMock(SomeClass1::class);
$param3 = $this->createParamMock('test', SomeInterface2::class);
$this->assertTrue($container->hasByParam($class3, $param3));
}
public function testTypedParamWithScalarBound1(): void
{
$container = BindingContainerBuilder::create()
->inContext(Class0::class, function (ContextualBinder $binder): void {
$binder->bindValue('$dep', 'test');
})
->build();
$class = new ReflectionClass(Class0::class);
$constructor = $class->getConstructor();
$params = $constructor->getParameters();
$this->assertFalse($container->hasByParam($class, $params[0]));
}
public function testTypedParamWithScalarBound2(): void
{
$container = BindingContainerBuilder::create()
->inContext(Class0::class, function (ContextualBinder $binder): void {
$binder->bindValue(NamedKey::create('dep'), new Class1());
})
->build();
$class = new ReflectionClass(Class0::class);
$constructor = $class->getConstructor();
$params = $constructor->getParameters();
$binding = $container->getByParam($class, $params[0]);
$this->assertEquals(Binding::VALUE, $binding->getType());
$this->assertInstanceOf(Class1::class, $binding->getValue());
}
public function testBindingQualified(): void
{
$container = BindingContainerBuilder::create()
->bindImplementation(SomeInterface1::class, SomeClass1::class)
->bindImplementation(QualifiedClassKey::create(SomeInterface1::class, 'q1'), SomeClass1Alt::class)
->bindImplementation(QualifiedClassKey::create(SomeInterface2::class, 'q3'), SomeClass2Alt::class)
->inContext(SomeClass1::class, function (ContextualBinder $binder): void {
$binder->bindImplementation(SomeInterface2::class, SomeClass2::class);
$binder->bindImplementation(QualifiedClassKey::create(SomeInterface2::class, 'q2'), SomeClass2Alt::class);
})
->build();
$param1 = $this->createParamMock('test', SomeInterface1::class, 'q1');
$this->assertTrue($container->hasByParam(null, $param1));
$binding = $container->getByParam(null, $param1);
$this->assertEquals(SomeClass1Alt::class, $binding->getValue());
//
$param2 = $this->createParamMock('test', SomeInterface1::class);
$this->assertTrue($container->hasByParam(null, $param2));
$binding = $container->getByParam(null, $param2);
$this->assertEquals(SomeClass1::class, $binding->getValue());
//
$contextClass = $this->createClassMock(SomeClass1::class);
//
$param3 = $this->createParamMock('test', SomeInterface2::class, 'q2');
$this->assertTrue($container->hasByParam($contextClass, $param3));
$binding = $container->getByParam($contextClass, $param3);
$this->assertEquals(SomeClass2Alt::class, $binding->getValue());
//
$param4 = $this->createParamMock('test', SomeInterface2::class);
$this->assertTrue($container->hasByParam($contextClass, $param4));
$binding = $container->getByParam($contextClass, $param4);
$this->assertEquals(SomeClass2::class, $binding->getValue());
//
$param3 = $this->createParamMock('test', SomeInterface2::class, 'q3');
$this->assertTrue($container->hasByParam($contextClass, $param3));
$binding = $container->getByParam($contextClass, $param3);
$this->assertEquals(SomeClass2Alt::class, $binding->getValue());
}
}

View File

@@ -0,0 +1,216 @@
<?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\Console;
use Espo\Core\ApplicationUser;
use Espo\Core\Console\Exceptions\InvalidArgument;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Metadata;
use Espo\Core\Console\CommandManager;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use PHPUnit\Framework\TestCase;
class CommandManagerTest extends TestCase
{
private ?InjectableFactory $injectableFactory = null;
private ?Metadata $metadata = null;
private ?CommandManager $manager = null;
private $command;
protected function setUp() : void
{
$this->injectableFactory = $this->createMock(InjectableFactory::class);
$this->metadata = $this->createMock(Metadata::class);
$applicationUser = $this->createMock(ApplicationUser::class);
$this->manager = new CommandManager($this->injectableFactory, $this->metadata, $applicationUser);
$this->command = $this->createMock(Command::class);
}
private function initTest(?array $allowedOptions = null, ?array $allowedFlags = null)
{
$className = 'Test';
$map = [
[['app', 'consoleCommands', 'commandName', 'className'], null, $className],
[['app', 'consoleCommands', 'commandName', 'noSystemUser'], null, false],
];
if ($allowedOptions !== null) {
$map[] = [['app', 'consoleCommands', 'commandName', 'allowedOptions'], null, $allowedOptions];
}
if ($allowedFlags!== null) {
$map[] = [['app', 'consoleCommands', 'commandName', 'allowedFlags'], null, $allowedFlags];
}
$this->metadata
->expects($this->any())
->method('get')
->willReturnMap($map);
$this->injectableFactory
->expects($this->any())
->method('create')
->with($className)
->willReturn($this->command);
}
public function testWithCommandPhp(): void
{
$argv = ['command.php', 'command-name', 'a1', 'a2', '--flag', '--flag-a', '-f', '--option-one=test'];
$this->initTest();
$expectedParams = new Params(
[
'optionOne' => 'test',
],
['flag', 'flagA', 'f'],
['a1', 'a2']
);
$io = new IO();
$this->command
->expects($this->once())
->method('run')
->with($expectedParams, $io);
$this->manager->run($argv);
}
public function testWithoutCommandPhp(): void
{
$argv = ['bin/command', 'command-name', 'a1', 'a2', '--flag', '--flag-a', '-f', '--option-one=test'];
$this->initTest();
$expectedParams = new Params(
[
'optionOne' => 'test',
],
['flag', 'flagA', 'f'],
['a1', 'a2']
);
$io = new IO();
$this->command
->expects($this->once())
->method('run')
->with($expectedParams, $io);
$this->manager->run($argv);
}
public function testAllowedOptions(): void
{
$argv = ['bin/command', 'command-name', '--option-one=test'];
$this->initTest(['optionOne']);
$expectedParams = new Params(
[
'optionOne' => 'test',
],
[],
[]
);
$io = new IO();
$this->command
->expects($this->once())
->method('run')
->with($expectedParams, $io);
$this->manager->run($argv);
}
public function testNotAllowedOptions(): void
{
$argv = ['bin/command', 'command-name', '--option-bad=test'];
$this->initTest(['optionOne']);
$this->expectException(InvalidArgument::class);
$this->manager->run($argv);
}
public function testAllowedFlags(): void
{
$argv = ['bin/command', 'command-name', '--flagOne', '-a'];
$this->initTest(null, ['flagOne', 'a']);
$expectedParams = new Params(
[],
['flagOne', 'a'],
[]
);
$io = new IO();
$this->command
->expects($this->once())
->method('run')
->with($expectedParams, $io);
$this->manager->run($argv);
}
public function testNotAllowedFlags1(): void
{
$argv = ['bin/command', 'command-name', '--bad-flag'];
$this->initTest(null, ['flag1']);
$this->expectException(InvalidArgument::class);
$this->manager->run($argv);
}
public function testNotAllowedFlags2(): void
{
$argv = ['bin/command', 'command-name', '-b'];
$this->initTest(null, ['a']);
$this->expectException(InvalidArgument::class);
$this->manager->run($argv);
}
}

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\Console;
use Espo\Core\Console\IO;
class IOTest extends \PHPUnit\Framework\TestCase
{
public function testExitStatus(): void
{
$io = new IO();
$this->assertEquals(0, $io->getExitStatus());
$io->setExitStatus(1);
$this->assertEquals(1, $io->getExitStatus());
}
}

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\unit\Espo\Core\Console;
use Espo\Core\Console\Command\Params;
class ParamsTest extends \PHPUnit\Framework\TestCase
{
public function testParams()
{
$raw = [
'argumentList' => ['a1', 'a2'],
'flagList' => ['f1', 'f2', 'f3'],
'options' => [
'optionOne' => 'test',
],
];
$params = new Params(
$raw['options'],
$raw['flagList'],
$raw['argumentList']
);
$this->assertEquals($raw['argumentList'], $params->getArgumentList());
$this->assertEquals($raw['flagList'], $params->getFlagList());
$this->assertEquals($raw['options'], $params->getOptions());
$this->assertTrue($params->hasFlag('f1'));
$this->assertFalse($params->hasFlag('f0'));
$this->assertTrue($params->hasOption('optionOne'));
$this->assertFalse($params->hasOption('optionZero'));
$this->assertEquals('test', $params->getOption('optionOne'));
$this->assertEquals(null, $params->getOption('optionTwo'));
$this->assertEquals('a1', $params->getArgument(0));
$this->assertEquals(null, $params->getArgument(4));
}
}

View File

@@ -0,0 +1,55 @@
<?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\Currency;
use Espo\Core\Currency\Rates;
class RatesTest extends \PHPUnit\Framework\TestCase
{
public function testRates1(): void
{
$rates = Rates
::create('USD')
->withRate('EUR', 1.2);
$this->assertEquals('USD', $rates->getBase());
$this->assertEquals(true, $rates->hasRate('EUR'));
$this->assertEquals(1.2, $rates->getRate('EUR'));
$this->assertEquals(['EUR' => 1.2, 'USD' => 1.0], $rates->toAssoc());
}
public function testRates2(): void
{
$rates = Rates::fromAssoc(['EUR' => 1.2], 'USD');
$this->assertEquals(['EUR' => 1.2, 'USD' => 1.0], $rates->toAssoc());
}
}

View File

@@ -0,0 +1,159 @@
<?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\Field\Address;
use Espo\Core\{
Field\Address,
};
use Espo\Classes\{
AddressFormatters\Formatter1,
AddressFormatters\Formatter2,
AddressFormatters\Formatter3,
AddressFormatters\Formatter4,
};
class AddressFormattersTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
}
public function testFormat1All()
{
$address = Address::createBuilder()
->setStreet('street')
->setCity('city')
->setCountry('country')
->setState('state')
->setPostalCode('postalCode')
->build();
$formatter = new Formatter1();
$expected =
"street\n" .
"city, state postalCode\n" .
"country";
$result = $formatter->format($address);
$this->assertEquals($expected, $result);
}
public function testFormat1NoState()
{
$address = Address::createBuilder()
->setStreet('street')
->setCity('city')
->setCountry('country')
->setState(null)
->setPostalCode('postalCode')
->build();
$formatter = new Formatter1();
$expected =
"street\n" .
"city postalCode\n" .
"country";
$result = $formatter->format($address);
$this->assertEquals($expected, $result);
}
public function testFormat2All()
{
$address = Address::createBuilder()
->setStreet('street')
->setCity('city')
->setCountry('country')
->setState('state')
->setPostalCode('postalCode')
->build();
$formatter = new Formatter2();
$expected =
"street\n" .
"postalCode city\n" .
"state country";
$result = $formatter->format($address);
$this->assertEquals($expected, $result);
}
public function testFormat3All()
{
$address = Address::createBuilder()
->setStreet('street')
->setCity('city')
->setCountry('country')
->setState('state')
->setPostalCode('postalCode')
->build();
$formatter = new Formatter3();
$expected =
"country\n" .
"state postalCode city\n" .
"street";
$result = $formatter->format($address);
$this->assertEquals($expected, $result);
}
public function testFormat4All()
{
$address = Address::createBuilder()
->setStreet('street')
->setCity('city')
->setCountry('country')
->setState('state')
->setPostalCode('postalCode')
->build();
$formatter = new Formatter4();
$expected =
"street\n" .
"city\n" .
"country - state postalCode";
$result = $formatter->format($address);
$this->assertEquals($expected, $result);
}
}

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\Field\Address;
use Espo\Core\{
Field\Address,
Field\Address\AddressFactory,
};
use Espo\ORM\Entity;
class AddressTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
}
public function testAddress1()
{
$address = Address::createBuilder()
->setStreet('street')
->setCity('city')
->setCountry('country')
->setState('state')
->setPostalCode('postalCode')
->build();
$this->assertEquals('street', $address->getStreet());
$this->assertEquals('city', $address->getCity());
$this->assertEquals('country', $address->getCountry());
$this->assertEquals('state', $address->getState());
$this->assertEquals('postalCode', $address->getPostalCode());
}
public function testBuilderClone()
{
$addressOriginal = Address::createBuilder()
->setStreet('street')
->setCity('city')
->setCountry('country')
->setState('state')
->setPostalCode('postalCode')
->build();
$address = Address::createBuilder()
->clone($addressOriginal)
->build();
$this->assertEquals('street', $address->getStreet());
$this->assertEquals('city', $address->getCity());
$this->assertEquals('country', $address->getCountry());
$this->assertEquals('state', $address->getState());
$this->assertEquals('postalCode', $address->getPostalCode());
}
public function testAddressWith()
{
$addressOriginal = Address::createBuilder()
->setStreet('street')
->setCity('city')
->setCountry('country')
->setState('state')
->setPostalCode('postalCode')
->build();
$address = $addressOriginal->withStreet('new street');
$this->assertEquals('new street', $address->getStreet());
$this->assertEquals('city', $address->getCity());
}
public function testCreateFromEntity()
{
$entity = $this->createMock(Entity::class);
$entity
->expects($this->any())
->method('get')
->willReturnMap([
['addressStreet', 'street'],
['addressCity', 'city'],
['addressCountry', 'country'],
['addressState', null],
['addressPostalCode', null],
]);
$factory = new AddressFactory();
$address = $factory->createFromEntity($entity, 'address');
$this->assertEquals('street', $address->getStreet());
$this->assertEquals('city', $address->getCity());
$this->assertEquals('country', $address->getCountry());
$this->assertEquals(null, $address->getState());
$this->assertEquals(null, $address->getPostalCode());
}
public function testCreateFromNothing()
{
$address = Address::create();
$this->assertEquals(null, $address->getState());
}
}

View File

@@ -0,0 +1,176 @@
<?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\Field\Currency;
use Espo\Core\Currency\ConfigDataProvider as CurrencyConfigDataProvider;
use Espo\Core\Currency\InternalRatesProvider;
use Espo\Core\Utils\Config;
use PHPUnit\Framework\TestCase;
use RuntimeException;
class CurrencyConfigDataProviderTest extends TestCase
{
private $config;
private $provider;
private $ratesProvider;
protected function setUp() : void
{
$this->config = $this->createMock(Config::class);
$this->ratesProvider = $this->createMock(InternalRatesProvider::class);
$this->provider = new CurrencyConfigDataProvider(
$this->config,
$this->ratesProvider,
);
}
public function testDefaultCurrency()
{
$this->config
->expects($this->once())
->method('get')
->with('defaultCurrency')
->willReturn('USD');
$currency = $this->provider->getDefaultCurrency();
$this->assertEquals('USD', $currency);
}
public function testBaseCurrency()
{
$this->config
->expects($this->once())
->method('get')
->with('baseCurrency')
->willReturn('USD');
$currency = $this->provider->getBaseCurrency();
$this->assertEquals('USD', $currency);
}
public function testCurrencyList()
{
$this->config
->expects($this->once())
->method('get')
->with('currencyList')
->willReturn(['USD', 'EUR']);
$result = $this->provider->getCurrencyList();
$this->assertEquals(['USD', 'EUR'], $result);
}
public function testHasCurrency()
{
$this->config
->expects($this->once())
->method('get')
->with('currencyList')
->willReturn(['USD', 'EUR']);
$result = $this->provider->hasCurrency('EUR');
$this->assertTrue($result);
}
public function testCurrencyRate1()
{
$this->ratesProvider
->expects($this->any())
->method('get')
->willReturn([
'EUR' => 1.2,
]);
$invokedCount = $this->exactly(2);
$this->config
->expects($invokedCount)
->method('get')
->willReturnCallback(function ($param) use ($invokedCount) {
if ($invokedCount->numberOfInvocations() === 1) {
$this->assertEquals('baseCurrency', $param);
return 'USD';
}
if ($invokedCount->numberOfInvocations() === 2) {
$this->assertEquals('currencyList', $param);
return ['USD', 'EUR'];
}
throw new RuntimeException();
});
$result = $this->provider->getCurrencyRate('EUR');
$this->assertEquals(1.2, $result);
}
public function testCurrencyRate2()
{
$this->ratesProvider
->expects($this->any())
->method('get')
->willReturn([
'EUR' => 1.2,
]);
$invokedCount = $this->exactly(2);
$this->config
->expects($invokedCount)
->method('get')
->willReturnCallback(function ($param) use ($invokedCount) {
if ($invokedCount->numberOfInvocations() === 1) {
$this->assertEquals('baseCurrency', $param);
return 'USD';
}
if ($invokedCount->numberOfInvocations() === 2) {
$this->assertEquals('currencyList', $param);
return ['USD', 'EUR'];
}
throw new RuntimeException();
});
$result = $this->provider->getCurrencyRate('USD');
$this->assertEquals(1.0, $result);
}
}

View File

@@ -0,0 +1,154 @@
<?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\Field\Currency;
use Espo\Core\{
Field\Currency,
Currency\Converter as CurrencyConverter,
Currency\ConfigDataProvider as CurrencyConfigDataProvider,
Currency\Rates as CurrencyRates,
};
class CurrencyConverterTest extends \PHPUnit\Framework\TestCase
{
public function testConvert1()
{
$currencyConfigDataProvider = $this->createMock(CurrencyConfigDataProvider::class);
$currencyConfigDataProvider
->expects($this->any())
->method('hasCurrency')
->with('EUR')
->willReturn(true);
$currencyConfigDataProvider
->expects($this->any())
->method('getCurrencyRate')
->willReturnMap([
['USD', 1.0],
['EUR', 1.2],
]);
$value = new Currency(2.0, 'USD');
$converter = new CurrencyConverter($currencyConfigDataProvider);
$convertedValue = $converter->convert($value, 'EUR');
$this->assertEquals('EUR', $convertedValue->getCode());
$this->assertEquals(round(2.0 / 1.2, 10), round($convertedValue->getAmount(), 10));
}
public function testConvert2()
{
$currencyConfigDataProvider = $this->createMock(CurrencyConfigDataProvider::class);
$currencyConfigDataProvider
->expects($this->any())
->method('hasCurrency')
->with('EUR')
->willReturn(true);
$currencyConfigDataProvider
->expects($this->any())
->method('getCurrencyRate')
->willReturnMap([
['USD', 1.0],
['EUR', 1.2],
['UAH', 0.035],
]);
$value = new Currency(2.0, 'UAH');
$converter = new CurrencyConverter($currencyConfigDataProvider);
$convertedValue = $converter->convert($value, 'EUR');
$this->assertEquals('EUR', $convertedValue->getCode());
$this->assertEquals(round(2.0 * 0.035 / 1.2, 10), round($convertedValue->getAmount(), 10));
}
public function testConvertToDefault()
{
$currencyConfigDataProvider = $this->createMock(CurrencyConfigDataProvider::class);
$currencyConfigDataProvider
->expects($this->any())
->method('getDefaultCurrency')
->willReturn('USD');
$currencyConfigDataProvider
->expects($this->any())
->method('hasCurrency')
->with('USD')
->willReturn(true);
$currencyConfigDataProvider
->expects($this->any())
->method('getCurrencyRate')
->willReturnMap([
['USD', 1.0],
['EUR', 1.2],
]);
$value = new Currency(2.0, 'EUR');
$converter = new CurrencyConverter($currencyConfigDataProvider);
$convertedValue = $converter->convertToDefault($value);
$this->assertEquals('USD', $convertedValue->getCode());
$this->assertEquals(round(2.0 * 1.2, 10), round($convertedValue->getAmount(), 10));
}
public function testConvertWithRates()
{
$currencyConfigDataProvider = $this->createMock(CurrencyConfigDataProvider::class);
$rates = CurrencyRates::fromAssoc([
'USD' => 1.0,
'EUR' => 1.2,
'UAH' => 0.035,
]);
$value = new Currency(2.0, 'UAH');
$converter = new CurrencyConverter($currencyConfigDataProvider);
$convertedValue = $converter->convertWithRates($value, 'EUR', $rates);
$this->assertEquals('EUR', $convertedValue->getCode());
$this->assertEquals(round(2.0 * 0.035 / 1.2, 10), round($convertedValue->getAmount(), 10));
}
}

View File

@@ -0,0 +1,285 @@
<?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\Field\Currency;
use Espo\Core\Field\Currency;
use Espo\Core\Field\Currency\CurrencyFactory;
use Espo\ORM\Entity;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
class CurrencyTest extends TestCase
{
public function testValue1()
{
$value = Currency::create(2.0, 'USD');
$this->assertEquals(2.0, $value->getAmount());
$this->assertEquals('USD', $value->getCode());
}
public function testValue()
{
$value = Currency::create(0.5, 'USD');
$this->assertEquals('0.5', $value->getAmountAsString());
$this->assertEquals('USD', $value->getCode());
}
public function testAdd()
{
$value = (new Currency(2.0, 'USD'))->add(
new Currency(1.0, 'USD')
);
$this->assertEquals(3.0, $value->getAmount());
$this->assertEquals('USD', $value->getCode());
}
public function testSubtract()
{
$value = (new Currency(2.0, 'USD'))->subtract(
new Currency(3.0, 'USD')
);
$this->assertEquals(-1.0, $value->getAmount());
$this->assertEquals('USD', $value->getCode());
}
public function testMultiply1()
{
$value = (new Currency(2.0, 'USD'))->multiply(3.0);
$this->assertEquals(6.0, $value->getAmount());
$this->assertEquals('USD', $value->getCode());
}
public function testMultiply2()
{
$value = (new Currency(2.0, 'USD'))->multiply(0.5);
$this->assertEquals('1.00000000000000', $value->getAmountAsString());
$this->assertEquals('USD', $value->getCode());
}
public function testDivide1()
{
$value = (new Currency(6.0, 'USD'))->divide(3.0);
$this->assertEquals(2.0, $value->getAmount());
$this->assertEquals('USD', $value->getCode());
}
public function testDivide2()
{
$value = (new Currency(6.0, 'USD'))->divide(0.5);
$this->assertEquals('12.00000000000000', $value->getAmount());
$this->assertEquals('USD', $value->getCode());
}
public function testRound1()
{
$value = (new Currency(2.306, 'USD'))->round(2);
$this->assertEquals(2.31, $value->getAmount());
$this->assertEquals('USD', $value->getCode());
}
public function testRound2()
{
$value = (new Currency(2.306, 'USD'))->round(4);
$this->assertEquals(2.306, $value->getAmount());
}
public function testRound3()
{
$value = (new Currency(2.306, 'USD'))->round(0);
$this->assertEquals(2, $value->getAmount());
}
public function testRound4()
{
$value = (new Currency(-2.306, 'USD'))->round(2);
$this->assertEquals(-2.31, $value->getAmount());
}
public function testRound5()
{
$value = (new Currency(-2.5, 'USD'))->round(0);
$this->assertEquals(-3, $value->getAmount());
}
public function testBadAdd()
{
$this->expectException(InvalidArgumentException::class);
(new Currency(2.0, 'USD'))->add(
new Currency(1.0, 'EUR')
);
}
public function testGetBadCode()
{
$this->expectException(InvalidArgumentException::class);
new Currency(2.0, '');
}
public function testCreateFromEntity()
{
$entity = $this->createMock(Entity::class);
$entity
->expects($this->any())
->method('get')
->willReturnMap([
['test', 10.0],
['testCurrency', 'USD'],
]);
$entity
->expects($this->any())
->method('has')
->willReturnMap([
['test', true],
['testCurrency', true],
]);
$factory = new CurrencyFactory();
$value = $factory->createFromEntity($entity, 'test');
$this->assertEquals(10.0, $value->getAmount());
$this->assertEquals('USD', $value->getCode());
}
public function testCreatableFromEntityTrue()
{
$entity = $this->createMock(Entity::class);
$entity
->expects($this->any())
->method('get')
->willReturnMap([
['test', 5],
['testCurrency', 'USD'],
]);
$factory = new CurrencyFactory();
$this->assertTrue(
$factory->isCreatableFromEntity($entity, 'test')
);
}
public function testCreatableFromEntityFalse()
{
$entity = $this->createMock(Entity::class);
$entity
->expects($this->any())
->method('get')
->willReturnMap([
['test', 5.0],
['testCurrency', null],
]);
$factory = new CurrencyFactory();
$this->assertFalse(
$factory->isCreatableFromEntity($entity, 'test')
);
}
public function testCompare1(): void
{
$this->assertEquals(
1,
Currency::create(2.0, 'USD')
->compare(
Currency::create(1.0, 'USD')
)
);
}
public function testCompare2(): void
{
$this->assertEquals(
0,
Currency::create(2.1, 'USD')
->compare(
Currency::create(2.1, 'USD')
)
);
}
public function testCompare3(): void
{
$this->assertEquals(
-1,
Currency::create(2.1, 'USD')
->compare(
Currency::create(3.1, 'USD')
)
);
}
public function testCompare4(): void
{
$this->expectException(InvalidArgumentException::class);
Currency::create(2.1, 'EUR')
->compare(
Currency::create(2.1, 'USD')
);
}
public function testIsNegative1(): void
{
$this->assertTrue(Currency::create(-1.0, 'USD')->isNegative());
}
public function testIsNegative2(): void
{
$this->assertFalse(Currency::create(1.0, 'USD')->isNegative());
}
public function testIsNegative3(): void
{
$this->assertFalse(Currency::create(0.0, 'USD')->isNegative());
}
}

View File

@@ -0,0 +1,274 @@
<?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\Field\Date;
use Espo\Core\Field\Date;
use DateTimeImmutable;
use DateTimeZone;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use DateInterval;
class DateTest extends TestCase
{
public function testFromString()
{
$value = Date::fromString('2021-05-01');
$this->assertEquals('2021-05-01', $value->toString());
}
public function testFromDateTime()
{
$dt = new DateTimeImmutable('2021-05-01', new DateTimeZone('UTC'));
$value = Date::fromDateTime($dt);
$this->assertEquals('2021-05-01', $value->toString());
}
public function testBad1()
{
$this->expectException(InvalidArgumentException::class);
Date::fromString('2021-05-A');
}
public function testBad2()
{
$this->expectException(InvalidArgumentException::class);
Date::fromString('2021-05-1');
}
public function testEmpty()
{
$this->expectException(InvalidArgumentException::class);
Date::fromString('');
}
public function testGetDateTime()
{
$value = Date::fromString('2021-05-01');
$this->assertEquals('2021-05-01', $value->toDateTime()->format('Y-m-d'));
}
public function testGetMethods()
{
$value = Date::fromString('2021-05-01');
$dt = new DateTimeImmutable('2021-05-01', new DateTimeZone('UTC'));
$this->assertEquals(1, $value->getDay());
$this->assertEquals(5, $value->getMonth());
$this->assertEquals(2021, $value->getYear());
$this->assertEquals(6, $value->getDayOfWeek());
$this->assertEquals($dt->getTimestamp(), $value->toTimestamp());
}
public function testAdd()
{
$value = Date::fromString('2021-05-01');
$modifiedValue = $value->add(DateInterval::createFromDateString('1 day'));
$this->assertEquals('2021-05-02', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testSubtract()
{
$value = Date::fromString('2021-05-01');
$modifiedValue = $value->subtract(DateInterval::createFromDateString('1 day'));
$this->assertEquals('2021-04-30', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testModify()
{
$value = Date::fromString('2021-05-01');
$modifiedValue = $value->modify('+1 month');
$this->assertEquals('2021-06-01', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testDiff(): void
{
$value1 = Date::fromString('2021-05-01');
$value2 = Date::fromString('2021-05-02');
$this->assertEquals(1, $value1->diff($value2)->d);
$this->assertEquals(0, $value1->diff($value2)->invert);
}
public function testToday(): void
{
$value1 = Date::createToday();
$value2 = Date::createToday(new DateTimeZone('Europe/Kyiv'));
$this->assertEquals(0, $value1->diff($value2)->invert);
}
public function testComparison(): void
{
$value = Date::fromString('2021-05-01');
$this->assertTrue(
$value->isEqualTo(
$value->modify('+1 day')->modify('-1 day')
)
);
$this->assertFalse(
$value->isEqualTo(
$value->modify('+1 day')
)
);
$this->assertFalse(
$value->isGreaterThan(
$value->modify('+1 day')
)
);
$this->assertFalse(
$value->isLessThan(
$value->modify('-1 day')
)
);
$this->assertTrue(
$value->isGreaterThan(
$value->modify('-1 day')
)
);
$this->assertTrue(
$value->isLessThan(
$value->modify('+1 day')
)
);
$this->assertTrue(
$value->isGreaterThanOrEqualTo(
$value
)
);
$this->assertTrue(
$value->isLessThanOrEqualTo(
$value
)
);
$this->assertTrue(
$value->isGreaterThanOrEqualTo(
$value->modify('-1 day')
)
);
$this->assertTrue(
$value->isLessThanOrEqualTo(
$value->modify('+1 day')
)
);
}
public function testAddDays(): void
{
$value = Date::fromString('2023-01-01');
$this->assertEquals(
Date::fromString('2023-01-02'),
$value->addDays(1)
);
$this->assertEquals(
Date::fromString('2023-01-03'),
$value->addDays(2)
);
$this->assertEquals(
Date::fromString('2022-12-31'),
$value->addDays(-1)
);
}
public function testAddMonths(): void
{
$value = Date::fromString('2023-01-01');
$this->assertEquals(
Date::fromString('2023-02-01'),
$value->addMonths(1)
);
$this->assertEquals(
Date::fromString('2023-03-01'),
$value->addMonths(2)
);
$this->assertEquals(
Date::fromString('2022-12-01'),
$value->addMonths(-1)
);
}
public function testAddYears(): void
{
$value = Date::fromString('2023-01-01');
$this->assertEquals(
Date::fromString('2024-01-01'),
$value->addYears(1)
);
$this->assertEquals(
Date::fromString('2025-01-01'),
$value->addYears(2)
);
$this->assertEquals(
Date::fromString('2022-01-01'),
$value->addYears(-1)
);
}
}

View File

@@ -0,0 +1,408 @@
<?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\Field\DateTime;
use Espo\Core\Field\DateTime;
use DateTimeImmutable;
use DateTimeZone;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
use DateInterval;
class DateTimeTest extends TestCase
{
public function testFromString1()
{
$value = DateTime::fromString('2021-05-01 10:20:30');
$this->assertEquals('2021-05-01 10:20:30', $value->toString());
}
public function testFromString2()
{
$value = DateTime::fromString('2021-05-01 10:20');
$this->assertEquals('2021-05-01 10:20:00', $value->toString());
}
public function testFromDateTime1()
{
$dt = new DateTimeImmutable('2021-05-01 10:20:30', new DateTimeZone('UTC'));
$value = DateTime::fromDateTime($dt);
$this->assertEquals('2021-05-01 10:20:30', $value->toString());
}
public function testFromDateTime2()
{
$dt = new DateTimeImmutable('2021-05-01 10:20:30', new DateTimeZone('Europe/Kyiv'));
$value = DateTime::fromDateTime($dt);
$this->assertEquals('2021-05-01 07:20:30', $value->toString());
}
public function testBad1()
{
$this->expectException(InvalidArgumentException::class);
DateTime::fromString('2021-05-A 10:20:30');
}
public function testBad2()
{
$this->expectException(InvalidArgumentException::class);
DateTime::fromString('2021-05-1 10:20:30');
}
public function testEmpty()
{
$this->expectException(InvalidArgumentException::class);
DateTime::fromString('');
}
public function testGetDateTime()
{
$value = DateTime::fromString('2021-05-01 10:20:30');
$this->assertEquals('2021-05-01', $value->toDateTime()->format('Y-m-d'));
}
public function testGetMethods()
{
$value = DateTime::fromString('2021-05-01 10:20:30');
$dt = new DateTimeImmutable('2021-05-01 10:20:30', new DateTimeZone('UTC'));
$this->assertEquals(1, $value->getDay());
$this->assertEquals(5, $value->getMonth());
$this->assertEquals(2021, $value->getYear());
$this->assertEquals(6, $value->getDayOfWeek());
$this->assertEquals(10, $value->getHour());
$this->assertEquals(20, $value->getMinute());
$this->assertEquals(30, $value->getSecond());
$this->assertEquals($dt->getTimestamp(), $value->toTimestamp());
}
public function testAdd()
{
$value = DateTime::fromString('2021-05-01 10:20:30');
$modifiedValue = $value->add(DateInterval::createFromDateString('1 day'));
$this->assertEquals('2021-05-02 10:20:30', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testSubtract()
{
$value = DateTime::fromString('2021-05-01 10:20:30');
$modifiedValue = $value->subtract(DateInterval::createFromDateString('1 day'));
$this->assertEquals('2021-04-30 10:20:30', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testModify()
{
$value = DateTime::fromString('2021-05-01 10:20:30');
$modifiedValue = $value->modify('+1 month');
$this->assertEquals('2021-06-01 10:20:30', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testWithTimezone()
{
$value = DateTime
::fromString('2021-05-01 10:20:30')
->withTimezone(new DateTimeZone('Europe/Kyiv'));
$this->assertEquals('2021-05-01 10:20:30', $value->toString());
$this->assertEquals(13, $value->getHour());
}
public function getGetTimezone()
{
$value = DateTime
::fromString('2021-05-01 10:20:30')
->withTimezone(new DateTimeZone('Europe/Kyiv'));
$this->assertEquals(new DateTimeZone('Europe/Kyiv'), $value->getTimezone());
}
public function testDiff(): void
{
$value1 = DateTime::fromString('2021-05-01 10:10:30');
$value2 = DateTime::fromString('2021-05-01 10:20:30');
$this->assertEquals(10, $value1->diff($value2)->i);
$this->assertEquals(0, $value1->diff($value2)->invert);
}
public function testNow(): void
{
$value = DateTime::createNow();
$this->assertNotNull($value);
}
public function testWithTime(): void
{
$value = DateTime::fromString('2021-05-01 10:10:30');
$this->assertEquals(
'2021-05-01 00:00:00',
$value->withTime(0, 0, 0)->toString()
);
$this->assertEquals(
'2021-05-01 00:10:30',
$value->withTime(0, null, null)->toString()
);
$this->assertEquals(
'2021-05-01 10:00:00',
$value->withTime(null, 0)->toString()
);
$this->assertEquals(
'2021-05-01 10:00:10',
$value->withTime(null, 0, 10)->toString()
);
}
public function testComparison(): void
{
$value = DateTime::fromString('2021-05-01 10:10:30')
->withTimezone(new DateTimeZone('Europe/Kyiv'));
$this->assertTrue(
$value->isEqualTo(
$value->withTimezone(new DateTimeZone('UTC'))
)
);
$this->assertFalse(
$value->isEqualTo(
$value->modify('+1 minute')
)
);
$this->assertFalse(
$value->isGreaterThan(
$value->modify('+1 minute')
)
);
$this->assertFalse(
$value->isLessThan(
$value->modify('-1 minute')
)
);
$this->assertTrue(
$value->isGreaterThan(
$value->modify('-1 minute')
)
);
$this->assertTrue(
$value->isLessThan(
$value->modify('+1 minute')
)
);
$this->assertTrue(
$value->isGreaterThanOrEqualTo(
$value
)
);
$this->assertTrue(
$value->isLessThanOrEqualTo(
$value
)
);
$this->assertTrue(
$value->isGreaterThanOrEqualTo(
$value->modify('-1 minute')
)
);
$this->assertTrue(
$value->isLessThanOrEqualTo(
$value->modify('+1 minute')
)
);
}
public function testAddDays(): void
{
$value = DateTime::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTime::fromString('2023-01-02 00:00:00'),
$value->addDays(1)
);
$this->assertEquals(
DateTime::fromString('2023-01-03 00:00:00'),
$value->addDays(2)
);
$this->assertEquals(
DateTime::fromString('2022-12-31 00:00:00'),
$value->addDays(-1)
);
}
public function testAddMonths(): void
{
$value = DateTime::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTime::fromString('2023-02-01 00:00:00'),
$value->addMonths(1)
);
$this->assertEquals(
DateTime::fromString('2023-03-01 00:00:00'),
$value->addMonths(2)
);
$this->assertEquals(
DateTime::fromString('2022-12-01 00:00:00'),
$value->addMonths(-1)
);
}
public function testAddYears(): void
{
$value = DateTime::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTime::fromString('2024-01-01 00:00:00'),
$value->addYears(1)
);
$this->assertEquals(
DateTime::fromString('2025-01-01 00:00:00'),
$value->addYears(2)
);
$this->assertEquals(
DateTime::fromString('2022-01-01 00:00:00'),
$value->addYears(-1)
);
}
public function testAddHours(): void
{
$value = DateTime::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTime::fromString('2023-01-01 01:00:00'),
$value->addHours(1)
);
$this->assertEquals(
DateTime::fromString('2022-12-31 23:00:00'),
$value->addHours(-1)
);
$this->assertEquals(
$value,
$value->addHours(0)
);
}
public function testAddMinutes(): void
{
$value = DateTime::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTime::fromString('2023-01-01 00:01:00'),
$value->addMinutes(1)
);
$this->assertEquals(
DateTime::fromString('2022-12-31 23:59:00'),
$value->addMinutes(-1)
);
$this->assertEquals(
$value,
$value->addMinutes(0)
);
}
public function testAddSeconds(): void
{
$value = DateTime::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTime::fromString('2023-01-01 00:00:01'),
$value->addSeconds(1)
);
$this->assertEquals(
DateTime::fromString('2022-12-31 23:59:59'),
$value->addSeconds(-1)
);
$this->assertEquals(
$value,
$value->addSeconds(0)
);
}
public function testFromTimestamp(): void
{
$timestamp = 1664959621;
$value = DateTime::fromTimestamp($timestamp);
$this->assertEquals($timestamp, $value->toTimestamp());
}
}

View File

@@ -0,0 +1,231 @@
<?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\Field\DateTimeOptional;
use Espo\Core\Field\DateTimeOptional;
use DateTimeImmutable;
use DateTimeZone;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
use DateInterval;
class DateTimeOptionalDateTest extends TestCase
{
public function testFromString()
{
$value = DateTimeOptional::fromString('2021-05-01');
$this->assertEquals('2021-05-01', $value->toString());
$this->assertTrue($value->isAllDay());
}
public function testFromDateTime()
{
$dt = new DateTimeImmutable('2021-05-01', new DateTimeZone('UTC'));
$value = DateTimeOptional::fromDateTimeAllDay($dt);
$this->assertEquals('2021-05-01', $value->toString());
}
public function testBad1()
{
$this->expectException(InvalidArgumentException::class);
DateTimeOptional::fromString('2021-05-A');
}
public function testBad2()
{
$this->expectException(InvalidArgumentException::class);
DateTimeOptional::fromString('2021-05-1');
}
public function testEmpty()
{
$this->expectException(InvalidArgumentException::class);
DateTimeOptional::fromString('');
}
public function testGetDateTime()
{
$value = DateTimeOptional::fromString('2021-05-01');
$this->assertEquals('2021-05-01', $value->toDateTime()->format('Y-m-d'));
}
public function testGetTimezone()
{
$value = DateTimeOptional::fromString('2021-05-01');
$this->assertEquals(new DateTimeZone('UTC'), $value->toDateTime()->getTimezone());
}
public function testGetMethods()
{
$value = DateTimeOptional::fromString('2021-05-01');
$dt = new DateTimeImmutable('2021-05-01', new DateTimeZone('UTC'));
$this->assertEquals(1, $value->getDay());
$this->assertEquals(5, $value->getMonth());
$this->assertEquals(2021, $value->getYear());
$this->assertEquals(6, $value->getDayOfWeek());
$this->assertEquals($dt->getTimestamp(), $value->toTimestamp());
}
public function testAdd()
{
$value = DateTimeOptional::fromString('2021-05-01');
$modifiedValue = $value->add(DateInterval::createFromDateString('1 day'));
$this->assertEquals('2021-05-02', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testSubtract()
{
$value = DateTimeOptional::fromString('2021-05-01');
$modifiedValue = $value->subtract(DateInterval::createFromDateString('1 day'));
$this->assertEquals('2021-04-30', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testModify()
{
$value = DateTimeOptional::fromString('2021-05-01');
$modifiedValue = $value->modify('+1 month');
$this->assertEquals('2021-06-01', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testWithTimezone()
{
$value = DateTimeOptional
::fromString('2021-05-01')
->withTimezone(new DateTimeZone('Europe/Kyiv'));
$this->assertEquals('2021-05-01 00:00:00', $value->toString());
$this->assertEquals(new DateTimeZone('Europe/Kyiv'), $value->getTimezone());
$this->assertFalse($value->isAllDay());
}
public function testDiff(): void
{
$value1 = DateTimeOptional::fromString('2021-05-01');
$value2 = DateTimeOptional::fromString('2021-05-02');
$this->assertEquals(1, $value1->diff($value2)->d);
$this->assertEquals(0, $value1->diff($value2)->invert);
}
public function testToday(): void
{
$value1 = DateTimeOptional::createToday();
$value2 = DateTimeOptional::createToday(new DateTimeZone('Europe/Kyiv'));
$this->assertEquals(0, $value1->diff($value2)->invert);
}
public function testAddDays(): void
{
$value = DateTimeOptional::fromString('2023-01-01');
$this->assertEquals(
DateTimeOptional::fromString('2023-01-02'),
$value->addDays(1)
);
$this->assertEquals(
DateTimeOptional::fromString('2023-01-03'),
$value->addDays(2)
);
$this->assertEquals(
DateTimeOptional::fromString('2022-12-31'),
$value->addDays(-1)
);
}
public function testAddMonths(): void
{
$value = DateTimeOptional::fromString('2023-01-01');
$this->assertEquals(
DateTimeOptional::fromString('2023-02-01'),
$value->addMonths(1)
);
$this->assertEquals(
DateTimeOptional::fromString('2023-03-01'),
$value->addMonths(2)
);
$this->assertEquals(
DateTimeOptional::fromString('2022-12-01'),
$value->addMonths(-1)
);
}
public function testAddYears(): void
{
$value = DateTimeOptional::fromString('2023-01-01');
$this->assertEquals(
DateTimeOptional::fromString('2024-01-01'),
$value->addYears(1)
);
$this->assertEquals(
DateTimeOptional::fromString('2025-01-01'),
$value->addYears(2)
);
$this->assertEquals(
DateTimeOptional::fromString('2022-01-01'),
$value->addYears(-1)
);
}
}

View File

@@ -0,0 +1,437 @@
<?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\Field\DateTimeOptionalTest;
use Espo\Core\Field\DateTimeOptional;
use DateTimeImmutable;
use DateTimeZone;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
use DateInterval;
class DateTimeOptionalTest extends TestCase
{
public function testFromString1()
{
$value = DateTimeOptional::fromString('2021-05-01 10:20:30');
$this->assertEquals('2021-05-01 10:20:30', $value->toString());
$this->assertFalse($value->isAllDay());
}
public function testFromString2()
{
$value = DateTimeOptional::fromString('2021-05-01 10:20');
$this->assertEquals('2021-05-01 10:20:00', $value->toString());
$this->assertFalse($value->isAllDay());
}
public function testFromDateTime1()
{
$dt = new DateTimeImmutable('2021-05-01 10:20:30', new DateTimeZone('UTC'));
$value = DateTimeOptional::fromDateTime($dt);
$this->assertEquals('2021-05-01 10:20:30', $value->toString());
}
public function testFromDateTime2()
{
$dt = new DateTimeImmutable('2021-05-01 10:20:30', new DateTimeZone('Europe/Kyiv'));
$value = DateTimeOptional::fromDateTime($dt);
$this->assertEquals('2021-05-01 07:20:30', $value->toString());
}
public function testBad1()
{
$this->expectException(InvalidArgumentException::class);
DateTimeOptional::fromString('2021-05-A 10:20:30');
}
public function testBad2()
{
$this->expectException(InvalidArgumentException::class);
DateTimeOptional::fromString('2021-05-1 10:20:30');
}
public function testEmpty()
{
$this->expectException(InvalidArgumentException::class);
DateTimeOptional::fromString('');
}
public function testGetDateTime()
{
$value = DateTimeOptional::fromString('2021-05-01 10:20:30');
$this->assertEquals('2021-05-01', $value->toDateTime()->format('Y-m-d'));
}
public function testGetMethods()
{
$value = DateTimeOptional::fromString('2021-05-01 10:20:30');
$dt = new DateTimeImmutable('2021-05-01 10:20:30', new DateTimeZone('UTC'));
$this->assertEquals(1, $value->getDay());
$this->assertEquals(5, $value->getMonth());
$this->assertEquals(2021, $value->getYear());
$this->assertEquals(6, $value->getDayOfWeek());
$this->assertEquals(10, $value->getHour());
$this->assertEquals(20, $value->getMinute());
$this->assertEquals(30, $value->getSecond());
$this->assertEquals($dt->getTimestamp(), $value->getTimestamp());
}
public function testAdd()
{
$value = DateTimeOptional::fromString('2021-05-01 10:20:30');
$modifiedValue = $value->add(DateInterval::createFromDateString('1 day'));
$this->assertEquals('2021-05-02 10:20:30', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testSubtract()
{
$value = DateTimeOptional::fromString('2021-05-01 10:20:30');
$modifiedValue = $value->subtract(DateInterval::createFromDateString('1 day'));
$this->assertEquals('2021-04-30 10:20:30', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testModify()
{
$value = DateTimeOptional::fromString('2021-05-01 10:20:30');
$modifiedValue = $value->modify('+1 month');
$this->assertEquals('2021-06-01 10:20:30', $modifiedValue->toString());
$this->assertNotSame($modifiedValue, $value);
}
public function testWithTimezone()
{
$value = DateTimeOptional
::fromString('2021-05-01 10:20:30')
->withTimezone(new DateTimeZone('Europe/Kyiv'));
$this->assertEquals('2021-05-01 10:20:30', $value->toString());
$this->assertEquals(13, $value->getHour());
}
public function getGetTimezone()
{
$value = DateTimeOptional
::fromString('2021-05-01 10:20:30')
->withTimezone(new DateTimeZone('Europe/Kyiv'));
$this->assertEquals(new DateTimeZone('Europe/Kyiv'), $value->getTimezone());
}
public function testDiff(): void
{
$value1 = DateTimeOptional::fromString('2021-05-01 10:10:30');
$value2 = DateTimeOptional::fromString('2021-05-01 10:20:30');
$this->assertEquals(10, $value1->diff($value2)->i);
$this->assertEquals(0, $value1->diff($value2)->invert);
}
public function testNow(): void
{
$value = DateTimeOptional::createNow();
$this->assertNotNull($value);
}
public function testWithTime1(): void
{
$value = DateTimeOptional::fromString('2021-05-01 10:10:30');
$this->assertEquals(
'2021-05-01 00:00:00',
$value->withTime(0, 0, 0)->toString()
);
$this->assertEquals(
'2021-05-01 00:10:30',
$value->withTime(0, null, null)->toString()
);
$this->assertEquals(
'2021-05-01 10:00:00',
$value->withTime(null, 0)->toString()
);
$this->assertEquals(
'2021-05-01 10:00:10',
$value->withTime(null, 0, 10)->toString()
);
}
public function testWithTime2(): void
{
$value = DateTimeOptional::fromString('2021-05-01');
$this->assertEquals(
'2021-05-01 00:00:00',
$value->withTime(0, 0, 0)->toString()
);
$this->assertEquals(
'2021-05-01 00:00:00',
$value->withTime(0, null, null)->toString()
);
$this->assertEquals(
'2021-05-01 00:00:00',
$value->withTime(null, 0)->toString()
);
$this->assertEquals(
'2021-05-01 00:00:10',
$value->withTime(null, 0, 10)->toString()
);
}
public function testComparison(): void
{
$value = DateTimeOptional::fromString('2021-05-01 10:10:30')
->withTimezone(new DateTimeZone('Europe/Kyiv'));
$this->assertTrue(
$value->isEqualTo(
$value->withTimezone(new DateTimeZone('UTC'))
)
);
$this->assertFalse(
$value->isEqualTo(
$value->modify('+1 minute')
)
);
$this->assertFalse(
$value->isGreaterThan(
$value->modify('+1 minute')
)
);
$this->assertFalse(
$value->isLessThan(
$value->modify('-1 minute')
)
);
$this->assertTrue(
$value->isGreaterThan(
$value->modify('-1 minute')
)
);
$this->assertTrue(
$value->isLessThan(
$value->modify('+1 minute')
)
);
$this->assertTrue(
$value->isGreaterThanOrEqualTo(
$value
)
);
$this->assertTrue(
$value->isLessThanOrEqualTo(
$value
)
);
$this->assertTrue(
$value->isGreaterThanOrEqualTo(
$value->modify('-1 minute')
)
);
$this->assertTrue(
$value->isLessThanOrEqualTo(
$value->modify('+1 minute')
)
);
}
public function testAddDays(): void
{
$value = DateTimeOptional::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTimeOptional::fromString('2023-01-02 00:00:00'),
$value->addDays(1)
);
$this->assertEquals(
DateTimeOptional::fromString('2023-01-03 00:00:00'),
$value->addDays(2)
);
$this->assertEquals(
DateTimeOptional::fromString('2022-12-31 00:00:00'),
$value->addDays(-1)
);
}
public function testAddMonths(): void
{
$value = DateTimeOptional::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTimeOptional::fromString('2023-02-01 00:00:00'),
$value->addMonths(1)
);
$this->assertEquals(
DateTimeOptional::fromString('2023-03-01 00:00:00'),
$value->addMonths(2)
);
$this->assertEquals(
DateTimeOptional::fromString('2022-12-01 00:00:00'),
$value->addMonths(-1)
);
}
public function testAddYears(): void
{
$value = DateTimeOptional::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTimeOptional::fromString('2024-01-01 00:00:00'),
$value->addYears(1)
);
$this->assertEquals(
DateTimeOptional::fromString('2025-01-01 00:00:00'),
$value->addYears(2)
);
$this->assertEquals(
DateTimeOptional::fromString('2022-01-01 00:00:00'),
$value->addYears(-1)
);
}
public function testAddHours(): void
{
$value = DateTimeOptional::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTimeOptional::fromString('2023-01-01 01:00:00'),
$value->addHours(1)
);
$this->assertEquals(
DateTimeOptional::fromString('2022-12-31 23:00:00'),
$value->addHours(-1)
);
$this->assertEquals(
$value,
$value->addHours(0)
);
}
public function testAddMinutes(): void
{
$value = DateTimeOptional::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTimeOptional::fromString('2023-01-01 00:01:00'),
$value->addMinutes(1)
);
$this->assertEquals(
DateTimeOptional::fromString('2022-12-31 23:59:00'),
$value->addMinutes(-1)
);
$this->assertEquals(
$value,
$value->addMinutes(0)
);
}
public function testAddSeconds(): void
{
$value = DateTimeOptional::fromString('2023-01-01 00:00:00');
$this->assertEquals(
DateTimeOptional::fromString('2023-01-01 00:00:01'),
$value->addSeconds(1)
);
$this->assertEquals(
DateTimeOptional::fromString('2022-12-31 23:59:59'),
$value->addSeconds(-1)
);
$this->assertEquals(
$value,
$value->addSeconds(0)
);
}
public function testFromTimestamp(): void
{
$timestamp = 1664959621;
$value = DateTimeOptional::fromTimestamp($timestamp);
$this->assertEquals($timestamp, $value->getTimestamp());
}
}

View File

@@ -0,0 +1,88 @@
<?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\Field\EmailAddress;
use Espo\Core\Field\EmailAddress;
use Espo\Core\Field\EmailAddress\EmailAddressGroupAttributeExtractor;
use Espo\Core\Field\EmailAddressGroup;
use PHPUnit\Framework\TestCase;
class EmailAddressGroupAttributeExtractorTest extends TestCase
{
public function testExtract()
{
$group = EmailAddressGroup
::create([
EmailAddress::create('ONE@test.com'),
EmailAddress::create('two@test.com')->optedOut(),
]);
$valueMap = (new EmailAddressGroupAttributeExtractor())->extract($group, 'emailAddress');
$this->assertEquals(
(object) [
'emailAddress' => 'ONE@test.com',
'emailAddressData' => [
(object) [
'emailAddress' => 'ONE@test.com',
'lower' => 'one@test.com',
'primary' => true,
'optOut' => false,
'invalid' => false,
],
(object) [
'emailAddress' => 'two@test.com',
'lower' => 'two@test.com',
'primary' => false,
'optOut' => true,
'invalid' => false,
],
]
],
$valueMap
);
}
public function testEmpty()
{
$group = EmailAddressGroup
::create([]);
$valueMap = (new EmailAddressGroupAttributeExtractor())->extract($group, 'emailAddress');
$this->assertEquals(
(object) [
'emailAddress' => null,
'emailAddressData' => [],
],
$valueMap
);
}
}

View File

@@ -0,0 +1,309 @@
<?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\Field\EmailAddress;
use Espo\Core\Field\EmailAddress\EmailAddressGroupFactory;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Repositories\EmailAddress as EmailAddressRepository;
use PHPUnit\Framework\TestCase;
use RuntimeException;
class EmailAddressGroupFactoryTest extends TestCase
{
/**
* @var Metadata
*/
private $metadata;
/**
* @var EmailAddressRepository
*/
private $emailAddressRepository;
/**
* @var EmailAddressGroupFactory
*/
private $factory;
public function setUp() : void
{
$this->metadata = $this->createMock(Metadata::class);
$entityManager = $this->createMock(EntityManager::class);
$this->emailAddressRepository = $this->createMock(EmailAddressRepository::class);
$this->factory = new EmailAddressGroupFactory($this->metadata, $entityManager);
$entityManager
->expects($this->any())
->method('getRepository')
->with('EmailAddress')
->willReturn($this->emailAddressRepository);
}
private function initField(string $entityType, string $field, string $type) : void
{
$this->metadata
->expects($this->any())
->method('get')
->with(['entityDefs', $entityType, 'fields', $field, 'type'])
->willReturn($type);
}
private function createEntityMock(string $entityType) : Entity
{
$entity = $this->createMock(Entity::class);
$entity
->expects($this->any())
->method('getEntityType')
->willReturn($entityType);
return $entity;
}
private function initEmailAddressRepository(Entity $entity, array $dataList) : void
{
$this->emailAddressRepository
->expects($this->once())
->method('getEmailAddressData')
->with($entity)
->willReturn($dataList);
}
public function testIsCreatableFromEntityTrue() : void
{
$this->initField('Test', 'test', 'email');
$entity = $this->createEntityMock('Test');
$this->assertTrue($this->factory->isCreatableFromEntity($entity, 'test'));
}
public function testIsCreatableFromEntityBad() : void
{
$this->initField('Test', 'test', 'varchar');
$entity = $this->createEntityMock('Test');
$this->assertFalse($this->factory->isCreatableFromEntity($entity, 'test'));
}
public function testCreateException() : void
{
$this->initField('Test', 'test', 'varchar');
$entity = $this->createEntityMock('Test');
$this->expectException(RuntimeException::class);
$this->factory->createFromEntity($entity, 'test');
}
public function testCreateFromEmailAddressRepository() : void
{
$this->initField('Test', 'test', 'email');
$entity = $this->createEntityMock('Test');
$dataList = [
(object) [
'emailAddress' => 'ONE@test.com',
'lower' => 'one@test.com',
'primary' => true,
'optOut' => false,
'invalid' => false,
],
(object) [
'emailAddress' => 'TWO@test.com',
'lower' => 'two@test.com',
'primary' => false,
'optOut' => false,
'invalid' => true,
],
(object) [
'emailAddress' => 'THREE@test.com',
'lower' => 'three@test.com',
'primary' => false,
'optOut' => true,
'invalid' => false,
],
];
$this->initEmailAddressRepository($entity, $dataList);
$entity
->method('has')
->willReturnMap(
[
['testData', false],
['test', true],
]
);
$entity
->method('get')
->willReturnMap(
[
['testData', null],
['test', 'ONE@test.com'],
]
);
$group = $this->factory->createFromEntity($entity, 'test');
$this->assertEquals(3, $group->getCount());
$one = $group->getList()[0];
$two = $group->getList()[1];
$three = $group->getList()[2];
$this->assertEquals('ONE@test.com', $one->getAddress());
$this->assertTrue($two->isInvalid());
$this->assertTrue($three->isOptedOut());
$this->assertEquals('ONE@test.com', $group->getPrimary()->getAddress());
}
public function testCreateFromDataAttribute() : void
{
$this->initField('Test', 'test', 'email');
$entity = $this->createEntityMock('Test');
$dataList = [
(object) [
'emailAddress' => 'ONE@test.com',
'lower' => 'one@test.com',
'primary' => true,
'optOut' => false,
'invalid' => false,
],
(object) [
'emailAddress' => 'TWO@test.com',
'lower' => 'two@test.com',
'primary' => false,
'optOut' => false,
'invalid' => true,
],
(object) [
'emailAddress' => 'THREE@test.com',
'lower' => 'three@test.com',
'primary' => false,
'optOut' => true,
'invalid' => false,
],
];
$entity
->expects($this->once())
->method('has')
->with('testData')
->willReturn(true);
$entity
->expects($this->once())
->method('get')
->with('testData')
->willReturn($dataList);
$group = $this->factory->createFromEntity($entity, 'test');
$this->assertEquals(3, $group->getCount());
$one = $group->getList()[0];
$two = $group->getList()[1];
$three = $group->getList()[2];
$this->assertEquals('ONE@test.com', $one->getAddress());
$this->assertTrue($two->isInvalid());
$this->assertTrue($three->isOptedOut());
$this->assertEquals('ONE@test.com', $group->getPrimary()->getAddress());
}
public function testCreateEmpty1() : void
{
$this->initField('Test', 'test', 'email');
$entity = $this->createEntityMock('Test');
$dataList = [];
$this->initEmailAddressRepository($entity, $dataList);
$group = $this->factory->createFromEntity($entity, 'test');
$this->assertEquals(0, $group->getCount());
}
public function testCreateEmpty2() : void
{
$this->initField('Test', 'test', 'email');
$entity = $this->createEntityMock('Test');
$dataList = [];
$this->initEmailAddressRepository($entity, $dataList);
$entity
->method('has')
->willReturnMap(
[
['testData', false],
['test', true],
]
);
$entity
->method('get')
->willReturnMap(
[
['testData', null],
['test', null],
]
);
$group = $this->factory->createFromEntity($entity, 'test');
$this->assertEquals(0, $group->getCount());
}
}

View File

@@ -0,0 +1,249 @@
<?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\Field\EmailAddress;
use Espo\Core\Field\EmailAddress;
use Espo\Core\Field\EmailAddressGroup;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
class EmailAddressGroupTest extends TestCase
{
public function testEmpty()
{
$group = EmailAddressGroup::create();
$this->assertEquals(0, count($group->getAddressList()));
$this->assertEquals(0, count($group->getSecondaryList()));
$this->assertNull($group->getPrimary());
$this->assertNull($group->getPrimaryAddress());
$this->assertEquals(0, $group->getCount());
}
public function testDuplicate1()
{
$this->expectException(InvalidArgumentException::class);
EmailAddressGroup
::create([
EmailAddress::create('one@test.com'),
EmailAddress::create('one@test.com'),
]);
}
public function testDuplicate2()
{
$this->expectException(InvalidArgumentException::class);
EmailAddressGroup
::create([
EmailAddress::create('one@test.com'),
EmailAddress::create('ONE@test.com'),
]);
}
public function testWithPrimary1()
{
$primary = EmailAddress::create('primary@test.com')->invalid();
$group = EmailAddressGroup
::create()
->withPrimary($primary);
$this->assertEquals(1, count($group->getList()));
$this->assertEquals(0, count($group->getSecondaryList()));
$this->assertNotNull($group->getPrimary());
$this->assertEquals('primary@test.com', $group->getPrimary()->getAddress());
$this->assertEquals('primary@test.com', $group->getPrimaryAddress());
$primaryAnother = EmailAddress::create('primaryAnother@test.com');
$groupAnother = $group->withPrimary($primaryAnother);
$this->assertEquals(2, count($groupAnother->getList()));
$this->assertEquals(1, count($groupAnother->getSecondaryList()));
$this->assertEquals('primaryAnother@test.com', $groupAnother->getPrimary()->getAddress());
$this->assertEquals('primary@test.com', $groupAnother->getList()[1]->getAddress());
$this->assertTrue($groupAnother->getList()[1]->isInvalid());
$this->assertTrue($groupAnother->hasAddress('primary@test.com'));
$this->assertTrue($groupAnother->hasAddress('primaryAnother@test.com'));
}
public function testWithPrimary2()
{
$address = EmailAddress::create('one@test.com')->invalid();
$group = EmailAddressGroup
::create([$address])
->withAdded(
EmailAddress::create('two@test.com')
)
->withPrimary(
EmailAddress::create('two@test.com')
);
$this->assertEquals('two@test.com', $group->getPrimary()->getAddress());
$this->assertEquals(2, count($group->getList()));
}
public function testWithAdded1()
{
$address = EmailAddress::create('one@test.com')->invalid();
$group = EmailAddressGroup
::create([$address])
->withAdded(
EmailAddress::create('two@test.com')
);
$this->assertEquals('one@test.com', $group->getPrimary()->getAddress());
$this->assertEquals(2, count($group->getList()));
$this->assertEquals(2, $group->getCount());
}
public function testWithAddedList()
{
$group = EmailAddressGroup
::create()
->withAddedList([
EmailAddress::create('one@test.com'),
EmailAddress::create('two@test.com'),
]);
$this->assertEquals('one@test.com', $group->getPrimary()->getAddress());
$this->assertEquals(2, count($group->getList()));
}
public function testWithRemoved1()
{
$group = EmailAddressGroup
::create([
EmailAddress::create('one@test.com'),
EmailAddress::create('two@test.com'),
EmailAddress::create('three@test.com'),
])
->withRemoved(EmailAddress::create('two@test.com'));
$this->assertEquals('one@test.com', $group->getPrimary()->getAddress());
$this->assertEquals(2, count($group->getList()));
}
public function testWithRemoved2()
{
$group = EmailAddressGroup
::create([
EmailAddress::create('one@test.com'),
EmailAddress::create('two@test.com'),
EmailAddress::create('three@test.com'),
])
->withRemoved(EmailAddress::create('one@test.com'));
$this->assertEquals('two@test.com', $group->getPrimary()->getAddress());
$this->assertEquals(2, count($group->getList()));
}
public function testWithRemoved3()
{
$group = EmailAddressGroup
::create([
EmailAddress::create('one@test.com'),
])
->withRemoved(EmailAddress::create('one@test.com'));
$this->assertNull($group->getPrimary());
$this->assertEquals(0, count($group->getList()));
$this->assertEquals(0, $group->getCount());
}
public function testHasAddress()
{
$group = EmailAddressGroup
::create([
EmailAddress::create('one@test.com'),
EmailAddress::create('two@test.com'),
]);
$this->assertTrue($group->hasAddress('one@test.com'));
$this->assertTrue($group->hasAddress('two@test.com'));
$this->assertFalse($group->hasAddress('four@test.com'));
}
public function testGetByAddress()
{
$group = EmailAddressGroup
::create([
EmailAddress::create('one@test.com'),
EmailAddress::create('two@test.com'),
EmailAddress::create('three@test.com'),
]);
$this->assertEquals('one@test.com', $group->getByAddress('one@test.com')->getAddress());
$this->assertNull($group->getByAddress('four@test.com'));
}
public function testClone()
{
$group = EmailAddressGroup
::create([
EmailAddress::create('one@test.com'),
EmailAddress::create('two@test.com'),
EmailAddress::create('three@test.com'),
]);
$cloned = clone $group;
$this->assertEquals('one@test.com', $cloned->getByAddress('one@test.com')->getAddress());
$this->assertEquals($cloned->getPrimary()->getAddress(), $group->getPrimary()->getAddress());
$this->assertNotSame($cloned->getPrimary(), $group->getPrimary());
$this->assertNotSame($cloned->getList()[1], $group->getList()[1]);
}
}

View File

@@ -0,0 +1,82 @@
<?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\Field\EmailAddress;
use Espo\Core\Field\EmailAddress;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
class EmailAddressTest extends TestCase
{
public function testInvalidAddress()
{
$this->expectException(InvalidArgumentException::class);
EmailAddress::create('one');
}
public function testInvalidEmpty()
{
$this->expectException(InvalidArgumentException::class);
EmailAddress::create('');
}
public function testCloneInvalid()
{
$address = EmailAddress::create('test@test.com')->invalid();
$this->assertEquals('test@test.com', $address->getAddress());
$this->assertTrue($address->isInvalid());
$this->assertFalse($address->isOptedOut());
}
public function testCloneOptedOut()
{
$address = EmailAddress::create('test@test.com')->optedOut();
$this->assertFalse($address->isInvalid());
$this->assertTrue($address->isOptedOut());
}
public function testCloneNotOptedOut()
{
$address = EmailAddress::create('test@test.com')
->optedOut()
->notOptedOut()
->invalid()
->notInvalid();
$this->assertFalse($address->isInvalid());
$this->assertFalse($address->isOptedOut());
}
}

View File

@@ -0,0 +1,60 @@
<?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\Field\Link;
use Espo\Core\Field\Link;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
class LinkTest extends TestCase
{
public function testCreate()
{
$value = Link::create('id');
$this->assertEquals('id', $value->getId());
$this->assertEquals(null, $value->getName());
}
public function testBad1()
{
$this->expectException(InvalidArgumentException::class);
Link::create('');
}
public function testWithName()
{
$value = Link::create('id')->withName('Name');
$this->assertEquals('Name', $value->getName());
}
}

View File

@@ -0,0 +1,179 @@
<?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\Field\LinkMultiple;
use Espo\Core\Field\LinkMultiple;
use Espo\Core\Field\LinkMultipleItem;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
class LinkMultipleTest extends TestCase
{
public function testEmpty()
{
$value = LinkMultiple::create();
$this->assertEquals(0, count($value->getIdList()));
$this->assertEquals(0, count($value->getList()));
$this->assertEquals(0, $value->getCount());
}
public function testDuplicate()
{
$this->expectException(InvalidArgumentException::class);
LinkMultiple
::create([
LinkMultipleItem::create('1'),
LinkMultipleItem::create('1'),
]);
}
public function testWithAdded1()
{
$item = LinkMultipleItem::create('1')->withName('test-1');
$group = LinkMultiple
::create([$item])
->withAdded(
LinkMultipleItem::create('2')
);
$this->assertEquals(2, $group->getCount());
$this->assertEquals('1', $group->getList()[0]->getId());
$this->assertEquals('test-1', $group->getList()[0]->getName());
$this->assertEquals(null, $group->getList()[1]->getName());
}
public function testWithAddedId()
{
$item = LinkMultipleItem::create('1')->withName('test-1');
$group = LinkMultiple
::create([$item])
->withAddedId('2');
$this->assertEquals(2, $group->getCount());
$this->assertEquals('1', $group->getList()[0]->getId());
$this->assertEquals('test-1', $group->getList()[0]->getName());
$this->assertEquals('2', $group->getList()[1]->getId());
}
public function testWithAddedIdList(): void
{
$item = LinkMultipleItem::create('1')->withName('test-1');
$group = LinkMultiple
::create([$item])
->withAddedIdList(['2', '3']);
$this->assertEquals(3, $group->getCount());
$this->assertEquals('1', $group->getList()[0]->getId());
$this->assertEquals('test-1', $group->getList()[0]->getName());
$this->assertEquals('2', $group->getList()[1]->getId());
}
public function testWithAddedList()
{
$group = LinkMultiple
::create()
->withAddedList([
LinkMultipleItem::create('1'),
LinkMultipleItem::create('2'),
]);
$this->assertEquals(2, $group->getCount());
}
public function testWithRemoved1()
{
$group = LinkMultiple
::create([
LinkMultipleItem::create('1'),
LinkMultipleItem::create('2'),
LinkMultipleItem::create('3'),
])
->withRemoved(LinkMultipleItem::create('1'));
$this->assertEquals(2, $group->getCount());
}
public function testGetById()
{
$group = LinkMultiple
::create([
LinkMultipleItem::create('1'),
LinkMultipleItem::create('2'),
LinkMultipleItem::create('3'),
]);
$this->assertEquals('1', $group->getById('1')->getId());
$this->assertNull($group->getById('4'));
}
public function testClone()
{
$group = LinkMultiple
::create([
LinkMultipleItem::create('1'),
LinkMultipleItem::create('2'),
LinkMultipleItem::create('3'),
]);
$cloned = clone $group;
$this->assertEquals('1', $cloned->getById('1')->getId());
$this->assertNotSame($cloned->getList()[1], $group->getList()[1]);
}
public function testItemColumn()
{
$item = LinkMultipleItem
::create('1')
->withColumnValue('key', 'value');
$this->assertTrue($item->hasColumnValue('key'));
$this->assertFalse($item->hasColumnValue('key-bad'));
$this->assertEquals('value', $item->getColumnValue('key'));
$this->assertEquals(['key'], $item->getColumnList());
}
}

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\unit\Espo\Core\Field\LinkParent;
use Espo\Core\Field\LinkParent;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
class LinkParentTest extends TestCase
{
public function testCreate()
{
$value = LinkParent::create('Test', 'id');
$this->assertEquals('Test', $value->getEntityType());
$this->assertEquals('id', $value->getId());
$this->assertEquals(null, $value->getName());
}
public function testBad1()
{
$this->expectException(InvalidArgumentException::class);
LinkParent::create('Test', '');
}
public function testBad2()
{
$this->expectException(InvalidArgumentException::class);
LinkParent::create('', 'id');
}
public function testWithName()
{
$value = LinkParent::create('Test', 'id')->withName('Name');
$this->assertEquals('Name', $value->getName());
}
}

View File

@@ -0,0 +1,88 @@
<?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\Field\PhoneNumber;
use Espo\Core\Field\PhoneNumber;
use Espo\Core\Field\PhoneNumber\PhoneNumberGroupAttributeExtractor;
use Espo\Core\Field\PhoneNumberGroup;
use PHPUnit\Framework\TestCase;
class PhoneNumberGroupAttributeExtractorTest extends TestCase
{
public function testExtract()
{
$group = PhoneNumberGroup
::create([
PhoneNumber::create('+1')->withType('Test-1'),
PhoneNumber::create('+2')->withType('Test-2')->optedOut(),
]);
$valueMap = (new PhoneNumberGroupAttributeExtractor())->extract($group, 'phoneNumber');
$this->assertEquals(
(object) [
'phoneNumber' => '+1',
'phoneNumberData' => [
(object) [
'phoneNumber' => '+1',
'type' => 'Test-1',
'primary' => true,
'optOut' => false,
'invalid' => false,
],
(object) [
'phoneNumber' => '+2',
'type' => 'Test-2',
'primary' => false,
'optOut' => true,
'invalid' => false,
],
]
],
$valueMap
);
}
public function testEmpty()
{
$group = PhoneNumberGroup
::create([]);
$valueMap = (new PhoneNumberGroupAttributeExtractor())->extract($group, 'phoneNumber');
$this->assertEquals(
(object) [
'phoneNumber' => null,
'phoneNumberData' => [],
],
$valueMap
);
}
}

View File

@@ -0,0 +1,317 @@
<?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\Field\PhoneNumber;
use Espo\Core\Field\PhoneNumber\PhoneNumberGroupFactory;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Repositories\PhoneNumber as PhoneNumberRepository;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
use RuntimeException;
class PhoneNumberGroupFactoryTest extends TestCase
{
/**
* @var Metadata
*/
private $metadata;
/**
* @var EntityManager
*/
private $entityManager;
/**
* @var PhoneNumberRepository
*/
private $phoneNumberRepository;
/**
* @var PhoneNumberGroupFactory
*/
private $factory;
public function setUp() : void
{
$this->metadata = $this->createMock(Metadata::class);
$this->entityManager = $this->createMock(EntityManager::class);
$this->phoneNumberRepository = $this->createMock(PhoneNumberRepository::class);
$this->factory = new PhoneNumberGroupFactory($this->metadata, $this->entityManager);
$this->entityManager
->expects($this->any())
->method('getRepository')
->with('PhoneNumber')
->willReturn($this->phoneNumberRepository);
}
private function initField(string $entityType, string $field, string $type) : void
{
$this->metadata
->expects($this->any())
->method('get')
->with(['entityDefs', $entityType, 'fields', $field, 'type'])
->willReturn($type);
}
private function createEntityMock(string $entityType) : Entity
{
$entity = $this->createMock(Entity::class);
$entity
->expects($this->any())
->method('getEntityType')
->willReturn($entityType);
return $entity;
}
private function initPhoneNumberRepository(Entity $entity, array $dataList) : void
{
$this->phoneNumberRepository
->expects($this->once())
->method('getPhoneNumberData')
->with($entity)
->willReturn($dataList);
}
public function testIsCreatableFromEntityTrue() : void
{
$this->initField('Test', 'test', 'phone');
$entity = $this->createEntityMock('Test');
$this->assertTrue($this->factory->isCreatableFromEntity($entity, 'test'));
}
public function testIsCreatableFromEntityBad() : void
{
$this->initField('Test', 'test', 'varchar');
$entity = $this->createEntityMock('Test');
$this->assertFalse($this->factory->isCreatableFromEntity($entity, 'test'));
}
public function testCreateException() : void
{
$this->initField('Test', 'test', 'varchar');
$entity = $this->createEntityMock('Test');
$this->expectException(RuntimeException::class);
$this->factory->createFromEntity($entity, 'test');
}
public function testCreateFromPhoneNumberRepository() : void
{
$this->initField('Test', 'test', 'phone');
$entity = $this->createEntityMock('Test');
$dataList = [
(object) [
'phoneNumber' => '+1',
'type' => 'Test-1',
'primary' => true,
'optOut' => false,
'invalid' => false,
],
(object) [
'phoneNumber' => '+2',
'type' => 'Test-2',
'primary' => false,
'optOut' => false,
'invalid' => true,
],
(object) [
'phoneNumber' => '+3',
'type' => 'Test-3',
'primary' => false,
'optOut' => true,
'invalid' => false,
],
];
$this->initPhoneNumberRepository($entity, $dataList);
$entity
->method('has')
->willReturnMap(
[
['testData', false],
['test', true],
]
);
$entity
->method('get')
->willReturnMap(
[
['testData', null],
['test', '+1'],
]
);
$group = $this->factory->createFromEntity($entity, 'test');
$this->assertEquals(3, $group->getCount());
$one = $group->getList()[0];
$two = $group->getList()[1];
$three = $group->getList()[2];
$this->assertEquals('+1', $one->getNumber());
$this->assertTrue($two->isInvalid());
$this->assertTrue($three->isOptedOut());
$this->assertEquals('Test-1', $one->getType());
$this->assertEquals('+1', $group->getPrimary()->getNumber());
}
public function testCreateFromDataAttribute() : void
{
$this->initField('Test', 'test', 'phone');
$entity = $this->createEntityMock('Test');
$dataList = [
(object) [
'phoneNumber' => '+1',
'type' => '1',
'primary' => true,
'optOut' => false,
'invalid' => false,
],
(object) [
'phoneNumber' => '+2',
'type' => '2',
'primary' => false,
'optOut' => false,
'invalid' => true,
],
(object) [
'phoneNumber' => '+3',
'type' => '3',
'primary' => false,
'optOut' => true,
'invalid' => false,
],
];
$entity
->expects($this->once())
->method('has')
->with('testData')
->willReturn(true);
$entity
->expects($this->once())
->method('get')
->with('testData')
->willReturn($dataList);
$group = $this->factory->createFromEntity($entity, 'test');
$this->assertEquals(3, $group->getCount());
$one = $group->getList()[0];
$two = $group->getList()[1];
$three = $group->getList()[2];
$this->assertEquals('+1', $one->getNumber());
$this->assertTrue($two->isInvalid());
$this->assertTrue($three->isOptedOut());
$this->assertEquals('+1', $group->getPrimary()->getNumber());
}
public function testCreateEmpty1() : void
{
$this->initField('Test', 'test', 'phone');
$entity = $this->createEntityMock('Test');
$dataList = [];
$this->initPhoneNumberRepository($entity, $dataList);
$group = $this->factory->createFromEntity($entity, 'test');
$this->assertEquals(0, $group->getCount());
}
public function testCreateEmpty2() : void
{
$this->initField('Test', 'test', 'phone');
$entity = $this->createEntityMock('Test');
$dataList = [];
$this->initPhoneNumberRepository($entity, $dataList);
$entity
->method('has')
->willReturnMap(
[
['testData', false],
['test', true],
]
);
$entity
->method('get')
->willReturnMap(
[
['testData', null],
['test', null],
]
);
$group = $this->factory->createFromEntity($entity, 'test');
$this->assertEquals(0, $group->getCount());
}
}

View File

@@ -0,0 +1,237 @@
<?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\Field\PhoneNumber;
use Espo\Core\Field\PhoneNumber;
use Espo\Core\Field\PhoneNumberGroup;
use PHPUnit\Framework\TestCase;
use InvalidArgumentException;
class PhoneNumberGroupTest extends TestCase
{
public function testEmpty()
{
$group = PhoneNumberGroup::create();
$this->assertEquals(0, count($group->getNumberList()));
$this->assertEquals(0, count($group->getSecondaryList()));
$this->assertNull($group->getPrimary());
$this->assertNull($group->getPrimaryNumber());
$this->assertEquals(0, $group->getCount());
}
public function testDuplicate()
{
$this->expectException(InvalidArgumentException::class);
PhoneNumberGroup
::create([
PhoneNumber::create('+100'),
PhoneNumber::create('+100'),
]);
}
public function testWithPrimary1()
{
$primary = PhoneNumber::create('+000')->invalid();
$group = PhoneNumberGroup
::create()
->withPrimary($primary);
$this->assertEquals(1, count($group->getList()));
$this->assertEquals(0, count($group->getSecondaryList()));
$this->assertNotNull($group->getPrimary());
$this->assertEquals('+000', $group->getPrimary()->getNumber());
$this->assertEquals('+000', $group->getPrimaryNumber());
$primaryAnother = PhoneNumber::create('+001');
$groupAnother = $group->withPrimary($primaryAnother);
$this->assertEquals(2, count($groupAnother->getList()));
$this->assertEquals(1, count($groupAnother->getSecondaryList()));
$this->assertEquals('+001', $groupAnother->getPrimary()->getNumber());
$this->assertEquals('+000', $groupAnother->getList()[1]->getNumber());
$this->assertTrue($groupAnother->getList()[1]->isInvalid());
$this->assertTrue($groupAnother->hasNumber('+000'));
$this->assertTrue($groupAnother->hasNumber('+001'));
}
public function testWithPrimary2()
{
$number = PhoneNumber::create('+100')->invalid();
$group = PhoneNumberGroup
::create([$number])
->withAdded(
PhoneNumber::create('+200')
)
->withPrimary(
PhoneNumber::create('+200')
);
$this->assertEquals('+200', $group->getPrimary()->getNumber());
$this->assertEquals(2, count($group->getList()));
}
public function testWithAdded1()
{
$number = PhoneNumber::create('+100')->invalid();
$group = PhoneNumberGroup
::create([$number])
->withAdded(
PhoneNumber::create('+200')
);
$this->assertEquals('+100', $group->getPrimary()->getNumber());
$this->assertEquals(2, count($group->getList()));
$this->assertEquals(2, $group->getCount());
}
public function testWithAddedList()
{
$group = PhoneNumberGroup
::create()
->withAddedList([
PhoneNumber::create('+100'),
PhoneNumber::create('+200'),
]);
$this->assertEquals('+100', $group->getPrimary()->getNumber());
$this->assertEquals(2, count($group->getList()));
}
public function testWithRemoved1()
{
$group = PhoneNumberGroup
::create([
PhoneNumber::create('+100'),
PhoneNumber::create('+200'),
PhoneNumber::create('+300'),
])
->withRemoved(PhoneNumber::create('+200'));
$this->assertEquals('+100', $group->getPrimary()->getNumber());
$this->assertEquals(2, count($group->getList()));
}
public function testWithRemoved2()
{
$group = PhoneNumberGroup
::create([
PhoneNumber::create('+100'),
PhoneNumber::create('+200'),
PhoneNumber::create('+300'),
])
->withRemoved(PhoneNumber::create('+100'));
$this->assertEquals('+200', $group->getPrimary()->getNumber());
$this->assertEquals(2, count($group->getList()));
}
public function testWithRemoved3()
{
$group = PhoneNumberGroup
::create([
PhoneNumber::create('+100'),
])
->withRemoved(PhoneNumber::create('+100'));
$this->assertNull($group->getPrimary());
$this->assertEquals(0, count($group->getList()));
$this->assertEquals(0, $group->getCount());
}
public function testHasNumber()
{
$group = PhoneNumberGroup
::create([
PhoneNumber::create('+100'),
PhoneNumber::create('+200'),
]);
$this->assertTrue($group->hasNumber('+100'));
$this->assertTrue($group->hasNumber('+200'));
$this->assertFalse($group->hasNumber('+400'));
}
public function testGetByNumber()
{
$group = PhoneNumberGroup
::create([
PhoneNumber::create('+100'),
PhoneNumber::create('+200'),
PhoneNumber::create('+300'),
]);
$this->assertEquals('+100', $group->getByNumber('+100')->getNumber());
$this->assertNull($group->getByNumber('+400'));
}
public function testClone()
{
$group = PhoneNumberGroup
::create([
PhoneNumber::create('+100'),
PhoneNumber::create('+200'),
PhoneNumber::create('+300'),
]);
$cloned = clone $group;
$this->assertEquals('+100', $cloned->getByNumber('+100')->getNumber());
$this->assertEquals($cloned->getPrimary()->getNumber(), $group->getPrimary()->getNumber());
$this->assertNotSame($cloned->getPrimary(), $group->getPrimary());
$this->assertNotSame($cloned->getList()[1], $group->getList()[1]);
}
}

View File

@@ -0,0 +1,85 @@
<?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\Field\PhoneNumber;
use Espo\Core\Field\PhoneNumber;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
class PhoneNumberTest extends TestCase
{
public function testInvalidEmpty()
{
$this->expectException(InvalidArgumentException::class);
PhoneNumber::create('');
}
public function testCloneInvalid()
{
$number = PhoneNumber::create('+100')->invalid();
$this->assertEquals('+100', $number->getNumber());
$this->assertTrue($number->isInvalid());
$this->assertFalse($number->isOptedOut());
$this->assertNull($number->getType());
}
public function testCloneOptedOut()
{
$number = PhoneNumber::create('+100')->optedOut();
$this->assertFalse($number->isInvalid());
$this->assertTrue($number->isOptedOut());
}
public function testCloneWithType()
{
$number = PhoneNumber::createWithType('+100', 'Office');
$this->assertEquals('+100', $number->getNumber());
$this->assertEquals('Office', $number->getType());
}
public function testCloneNotOptedOut()
{
$number = PhoneNumber::create('+100')
->optedOut()
->notOptedOut()
->invalid()
->notInvalid();
$this->assertFalse($number->isInvalid());
$this->assertFalse($number->isOptedOut());
}
}

View File

@@ -0,0 +1,64 @@
<?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\FieldProcessing;
use Espo\Core\{
FieldProcessing\Loader\Params as LoaderParams,
};
class LoaderParamsTest extends \PHPUnit\Framework\TestCase
{
public function testOne(): void
{
$select = [
'id',
'name',
];
$params = LoaderParams
::create()
->withSelect($select);
$this->assertEquals(true, $params->hasInSelect('id'));
$this->assertEquals(false, $params->hasInSelect('test'));
$this->assertEquals(true, $params->hasSelect());
}
public function testTwo(): void
{
$params = LoaderParams
::create();
$this->assertEquals(false, $params->hasSelect());
$this->assertEquals(false, $params->hasInSelect('test'));
}
}

View File

@@ -0,0 +1,55 @@
<?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\FieldProcessing;
use Espo\Core\{
FieldProcessing\Saver\Params,
ORM\Repository\Option\SaveOption};
class SaverParamsTest extends \PHPUnit\Framework\TestCase
{
public function testOne(): void
{
$options = [
SaveOption::SILENT => true,
];
$params = Params
::create()
->withRawOptions($options);
$this->assertEquals(true, $params->getOption('silent'));
$this->assertEquals(true, $params->hasOption('silent'));
$this->assertEquals(null, $params->getOption('skipHooks'));
$this->assertEquals(false, $params->hasOption('skipHooks'));
$this->assertEquals($options, $params->getRawOptions());
}
}

View File

@@ -0,0 +1,82 @@
<?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\Formula;
use Espo\Core\Formula\ArgumentList;
class ArgumentListTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
}
protected function tearDown() : void
{
}
public function testCount()
{
$list = new ArgumentList([]);
$this->assertEquals(0, count($list));
$list = new ArgumentList([
null,
'1',
(object) [],
]);
$this->assertEquals(3, count($list));
}
public function testAccess()
{
$list = new ArgumentList([
null,
'1',
(object) [],
]);
$this->assertEquals(null, $list[0]->getData());
$this->assertEquals('1', $list[1]->getData());
}
public function testIteration()
{
$list = new ArgumentList([
null,
'1',
]);
$array = [];
foreach ($list as $item) {
$array[] = $item->getData();
}
$this->assertEquals(null, $array[0]);
$this->assertEquals('1', $array[1]);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,380 @@
<?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\Hook;
use Espo\Core\Hook\DataProvider;
use PHPUnit\Framework\TestCase;
use tests\unit\ReflectionHelper;
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\Module\PathProvider;
class DataProviderTest extends TestCase
{
private string $filesPath = 'tests/unit/testData/Hooks';
private ?Config\SystemConfig $systemConfig = null;
private ?PathProvider $pathProvider = null;
private ?ReflectionHelper $reflectionHelper = null;
private ?Metadata $metadata = null;
protected function setUp(): void
{
$this->metadata = $this->createMock(Metadata::class);
$this->getMockBuilder(Metadata::class)->disableOriginalConstructor()->getMock();
$this->systemConfig = $this->createMock(Config\SystemConfig::class);
$dataCache = $this->createMock(DataCache::class);
$fileManager = new FileManager();
$this->pathProvider = $this->createMock(PathProvider::class);
$dataProvider = new DataProvider(
systemConfig: $this->systemConfig,
fileManager: $fileManager,
pathProvider: $this->pathProvider,
dataCache: $dataCache,
metadata: $this->metadata,
);
$this->reflectionHelper = new ReflectionHelper($dataProvider);
}
private function initPathProvider(string $folder): void
{
$this->pathProvider
->method('getCustom')
->willReturn($this->filesPath . '/' . $folder . '/custom/Espo/Custom/');
$this->pathProvider
->method('getCore')
->willReturn($this->filesPath . '/' . $folder . '/application/Espo/');
$this->pathProvider
->method('getModule')
->willReturnCallback(
function (?string $moduleName) use ($folder): string {
$path = $this->filesPath . '/' . $folder . '/application/Espo/Modules/{*}/';
if ($moduleName === null) {
return $path;
}
return str_replace('{*}', $moduleName, $path);
}
);
}
public function testHookExists(): void
{
$data = [
[
'className' => 'Espo\\Hooks\\Note\\Stream',
'order' => 8,
],
[
'className' => 'Espo\\Hooks\\Note\\Mentions',
'order' => 9,
],
[
'className' => 'Espo\\Hooks\\Note\\Notifications',
'order' => 14,
],
];
$this->assertTrue(
$this->reflectionHelper->invokeMethod('hookExists', ['Espo\\Hooks\\Note\\Mentions', $data])
);
$this->assertTrue(
$this->reflectionHelper->invokeMethod('hookExists', ['Espo\\Modules\\Crm\\Hooks\\Note\\Mentions', $data])
);
$this->assertTrue(
$this->reflectionHelper->invokeMethod('hookExists', ['Espo\\Modules\\Test\\Hooks\\Note\\Mentions', $data])
);
$this->assertTrue(
$this->reflectionHelper->invokeMethod('hookExists', ['Espo\\Modules\\Test\\Hooks\\Common\\Stream', $data])
);
$this->assertFalse(
$this->reflectionHelper->invokeMethod('hookExists', ['Espo\\Hooks\\Note\\TestHook', $data])
);
}
public function testSortHooks(): void
{
$data = [
'Common' =>
[
'afterSave' =>
[
[
'className' => 'Espo\\Hooks\\Common\\AssignmentEmailNotification',
'order' => 9,
],
[
'className' => 'Espo\\Hooks\\Common\\Notifications',
'order' => 10,
],
[
'className' => 'Espo\\Hooks\\Common\\Stream',
'order' => 9,
],
],
'beforeSave' =>
[
[
'className' => 'Espo\\Hooks\\Common\\Formula',
'order' => 5,
],
[
'className' => 'Espo\\Hooks\\Common\\NextNumber',
'order' => 10,
],
[
'className' => 'Espo\\Hooks\\Common\\CurrencyConverted',
'order' => 1,
],
],
],
'Note' =>
[
'beforeSave' =>
[
[
'className' => 'Espo\\Hooks\\Note\\Mentions',
'order' => 9,
],
],
'afterSave' =>
[
[
'className' => 'Espo\\Hooks\\Note\\Notifications',
'order' => 14,
],
],
],
];
$result = [
'Common' =>
[
'afterSave' =>
[
[
'className' => 'Espo\\Hooks\\Common\\AssignmentEmailNotification',
'order' => 9,
],
[
'className' => 'Espo\\Hooks\\Common\\Stream',
'order' => 9,
],
[
'className' => 'Espo\\Hooks\\Common\\Notifications',
'order' => 10,
],
],
'beforeSave' =>
[
[
'className' => 'Espo\\Hooks\\Common\\CurrencyConverted',
'order' => 1,
],
[
'className' => 'Espo\\Hooks\\Common\\Formula',
'order' => 5,
],
[
'className' => 'Espo\\Hooks\\Common\\NextNumber',
'order' => 10,
],
],
],
'Note' =>
[
'beforeSave' =>
[
[
'className' => 'Espo\\Hooks\\Note\\Mentions',
'order' => 9,
],
],
'afterSave' =>
[
[
'className' => 'Espo\\Hooks\\Note\\Notifications',
'order' => 14,
],
],
],
];
$this->assertEquals($result, $this->reflectionHelper->invokeMethod('sortHooks', [$data]));
}
public function testCase1CustomHook(): void
{
$this->initPathProvider('testCase1');
$this->systemConfig
->expects($this->exactly(2))
->method('useCache')
->willReturn(false);
$this->metadata
->expects($this->once())
->method('getModuleList')
->willReturn([
'Crm',
'Test',
]);
$this->reflectionHelper->invokeMethod('load');
$result = [
'Note' =>
[
'beforeSave' =>
[
[
'className' => 'tests\\unit\\testData\\Hooks\\testCase1\\custom\\Espo\\Custom\\Hooks\\Note\\Mentions',
'order' => 7,
],
],
],
];
$this->assertEquals($result, $this->reflectionHelper->getProperty('data'));
}
public function testCase2ModuleHook1(): void
{
$this->initPathProvider('testCase2');
$this->systemConfig
->expects($this->exactly(2))
->method('useCache')
->willReturn(false);
$this->metadata
->expects($this->once())
->method('getModuleList')
->willReturn([
'Crm',
'Test',
]);
$this->reflectionHelper->invokeMethod('load');
$result = [
'Note' =>
[
'beforeSave' =>
[
[
'className' =>
'tests\\unit\\testData\\Hooks\\testCase2\\application\\Espo\\Modules\\Crm\\Hooks\\Note\\Mentions',
'order' => 9,
],
],
],
];
$this->assertEquals($result, $this->reflectionHelper->getProperty('data'));
}
public function testCase2ModuleHookReverseModuleOrder(): void
{
$this->initPathProvider('testCase2');
$this->systemConfig
->expects($this->exactly(2))
->method('useCache')
->willReturn(false);
$this->metadata
->expects($this->once())
->method('getModuleList')
->willReturn([
'Test',
'Crm',
]);
$this->reflectionHelper->invokeMethod('load');
$result = [
'Note' =>
[
'beforeSave' =>
[
[
'className' =>
'tests\\unit\\testData\\Hooks\\testCase2\\application\\Espo\\Modules\\Test\\Hooks\\Note\\Mentions',
'order' => 9,
],
],
],
];
$this->assertEquals($result, $this->reflectionHelper->getProperty('data'));
}
public function testCase3CoreHook()
{
$this->initPathProvider('testCase3');
$this->systemConfig
->expects($this->exactly(2))
->method('useCache')
->willReturn(false);
$this->metadata
->expects($this->once())
->method('getModuleList')
->willReturn([]);
$this->reflectionHelper->invokeMethod('load');
$result = [
'Note' =>
[
'beforeSave' =>
[
[
'className' => 'tests\\unit\\testData\\Hooks\\testCase3\\application\\Espo\\Hooks\\Note\\Mentions',
'order' => 9,
],
],
],
];
$this->assertEquals($result, $this->reflectionHelper->getProperty('data'));
}
}

View File

@@ -0,0 +1,330 @@
<?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\Htmlizer;
use Espo\Core\AclManager;
use Espo\Core\Currency\PrecisionProvider;
use Espo\Core\Htmlizer\Htmlizer;
use Espo\Core\InjectableFactory;
use Espo\Core\Select\SelectBuilderFactory;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Language;
use Espo\Core\Utils\Log;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\NumberUtil;
use Espo\Core\Utils\DateTime;
use Espo\Core\ORM\Entity;
use Espo\Entities\User;
use Espo\ORM\Entity as EntityAlias;
use Espo\ORM\EntityManager;
use PHPUnit\Framework\TestCase;
use stdClass;
class HtmlizerTest extends TestCase
{
private ?Htmlizer $htmlizer;
private ?DateTime $dateTime;
private ?NumberUtil $number;
private ?EntityManager $entityManager;
private $entityAttributes = [
'id' => [
'type' => EntityAlias::ID,
],
'name' => [
'type' => EntityAlias::VARCHAR,
'len' => 255,
],
'date' => [
'type' => EntityAlias::DATE
],
'dateTime' => [
'type' => EntityAlias::DATETIME
],
'int' => [
'type' => EntityAlias::INT
],
'float' => [
'type' => EntityAlias::FLOAT
],
'list' => [
'type' => EntityAlias::JSON_ARRAY
],
'object' => [
'type' => EntityAlias::JSON_OBJECT
],
'deleted' => [
'type' => EntityAlias::BOOL,
'default' => 0,
]
];
protected function setUp(): void
{
date_default_timezone_set('UTC');
$this->entityManager = $this->createMock(EntityManager::class);
$this->dateTime = new DateTime('MM/DD/YYYY', 'hh:mm A', 'Europe/Kyiv');
$this->number = new NumberUtil('.', ',');
$this->htmlizer = $this->createHtmlizer();
}
private function createHtmlizer(?AclManager $aclManager = null)
{
return new Htmlizer(
dateTime: $this->dateTime,
number: $this->number,
selectBuilderFactory: $this->createMock(SelectBuilderFactory::class),
user: $this->createMock(User::class),
entityManager: $this->entityManager,
metadata: $this->createMock(Metadata::class),
language: $this->createMock(Language::class),
config: $this->createMock(Config::class),
log: $this->createMock(Log::class),
injectableFactory: $this->createMock(InjectableFactory::class),
precisionProvider: $this->createMock(PrecisionProvider::class),
aclManager: $aclManager ?? $this->createMock(AclManager::class),
);
}
protected function tearDown(): void
{}
public function testRender()
{
$entity = new Entity(
'Test',
[
'attributes' => $this->entityAttributes,
],
$this->entityManager
);
$entity->set('name', 'test');
$entity->set('date', '2015-09-15');
$entity->set('dateTime', '2015-09-15 10:00:00');
$entity->set('int', 3);
$entity->set('float', 3.5);
$item1 = new stdClass();
$item1->value = 1;
$item2 = new stdClass();
$item2->value = 2000.5;
$list = [$item1, $item2];
$entity->set('list', $list);
$template = "{{name}} test {{date}} {{dateTime}} {{#each list}}{{value}} {{/each}}{{int}} {{float}}";
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('test test 09/15/2015 09/15/2015 01:00 PM 1 2,000.50 3 3.50', $html);
//
$template = "{{float}}";
$entity->set('float', 3);
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('3.00', $html);
$entity->set('float', 3);
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('3.00', $html);
$entity->set('float', 10000.50);
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('10,000.50', $html);
//
$template = "{{int}}";
$entity->set('int', 3000);
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('3,000', $html);
//
$template = "{{float_RAW}}";
$entity->set('float', 10000.50);
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('10000.5', $html);
//
$template = "{{numberFormat float_RAW}}";
$entity->set('float', 10000.60);
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('10,001', $html);
//
$template = "{{numberFormat float_RAW decimals=2}}";
$entity->set('float', 10000.601);
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('10,000.60', $html);
//
$template = "{{numberFormat float_RAW decimals=0}}";
$entity->set('float', 10000.1);
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('10,000', $html);
//
$template = "{{numberFormat float_RAW decimals=2 decimalPoint='.' thousandsSeparator=' '}}";
$entity->set('float', 10000.60);
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('10 000.60', $html);
//
$template = "{{file name}}";
$entity->set('name', '1');
$html = $this->htmlizer->render($entity, $template, skipInlineAttachmentHandling: true);
$this->assertEquals('?entryPoint=attachment&id=1', $html);
//
$template = "{{#ifEqual name '1'}}hello{{/ifEqual}}";
$entity->set('name', '1');
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('hello', $html);
$template = "{{#ifNotEqual name '1'}}hello{{else}}test{{/ifNotEqual}}";
$entity->set('name', '1');
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('test', $html);
$template = "{{#if (and (equal name 'test') true 1)}}test{{/if}}";
$entity->set('name', 'test');
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('test', $html);
$template = "{{#if (or (equal name 'test') false 0)}}test{{/if}}";
$entity->set('name', 'test');
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('test', $html);
$template = "{{#if (not false)}}test{{/if}}";
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('test', $html);
$template = "{{#ifEqual name '1'}}{{#ifEqual name '1'}}hello{{/ifEqual}}{{/ifEqual}}";
$entity->set('name', '1');
$html = $this->htmlizer->render($entity, $template);
$this->assertEquals('hello', $html);
}
public function testIterate(): void
{
/** @noinspection HtmlUnknownAttribute */
$template = "<ul><li iterate=\"{{items}}\">{{name}}</li></ul><ul><li iterate=\"{{items}}\">{{name}}</li></ul>";
$html = $this->htmlizer->render(null, $template, [
'items' => [
['name' => '1'],
['name' => '2'],
],
]);
/** @noinspection HtmlUnknownAttribute */
$expected = "<ul><li>1</li><li>2</li></ul><ul><li>1</li><li>2</li></ul>";
$this->assertEquals($expected, $html);
}
public function testIf(): void
{
/** @noinspection HtmlUnknownAttribute */
$template = "<ul><li x-if=\"{{and (equal 1 1) true}}\">1</li><li x-if=\"{{false}}\">2</li>".
"<li x-if=\"{{1}}\">true</li></ul>";
$html = $this->htmlizer->render(null, $template, []);
/** @noinspection HtmlUnknownAttribute */
$expected = "<ul><li>1</li><li>true</li></ul>";
$this->assertEquals($expected, $html);
}
public function testAdditionalData(): void
{
$aclManager = $this->createMock(AclManager::class);
$aclManager->method('getScopeRestrictedAttributeList')
->willReturn(['int', 'float']);
$htmlizer = $this->createHtmlizer(
aclManager: $aclManager,
);
$entity = new Entity(
entityType: 'Test',
defs: [
'attributes' => $this->entityAttributes,
],
entityManager: $this->entityManager,
);
$entity->setMultiple([
'name' => 'test',
'date' => '2030-09-15',
'int' => 1,
'float' => 2.0,
]);
$additionalData = [
'date' => 't1',
'p1' => 'v1',
'int' => 'v2',
];
$template = "{{name}} {{date}} {{p1}} {{int}} {{float}}";
$html = $htmlizer->render($entity, $template, $additionalData);
$this->assertEquals('test t1 v1 v2 ', $html);
}
}

View File

@@ -0,0 +1,120 @@
<?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;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingContainerBuilder;
use Espo\Core\Binding\BindingData;
use Espo\Core\Container;
use Espo\Core\InjectableFactory;
use tests\integration\testClasses\Binding\SomeClass;
use tests\integration\testClasses\Binding\SomeImplementation;
use tests\integration\testClasses\Binding\SomeInterface;
use tests\unit\testClasses\Core\Binding\SomeClass0;
use tests\unit\testClasses\Core\Binding\SomeClass1;
use tests\unit\testClasses\Core\Binding\SomeClass2;
use tests\unit\testClasses\Core\Binding\SomeInterface1;
use tests\unit\testClasses\Core\Binding\SomeInterface2;
class InjectableFactoryTest extends \PHPUnit\Framework\TestCase
{
public function testCreateWithBinding1(): void
{
$container = $this->createMock(Container::class);
$injectableFactory = new InjectableFactory($container);
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$instance = $this->createMock(SomeInterface::class);
$binder->bindInstance(SomeInterface::class, $instance);
$obj = $injectableFactory->createWithBinding(SomeClass::class, new BindingContainer($bindingData));
$this->assertNotNull($obj);
$this->assertSame($instance, $obj->get());
}
public function testCreateWithBinding2(): void
{
$container = $this->createMock(Container::class);
$injectableFactory = new InjectableFactory($container);
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindImplementation(SomeInterface1::class, SomeClass1::class)
->bindImplementation(SomeInterface2::class, SomeClass2::class);
$obj = $injectableFactory->createWithBinding(SomeClass0::class, new BindingContainer($bindingData));
$this->assertNotNull($obj);
}
public function testCreateResolved1(): void
{
$container = $this->createMock(Container::class);
$bindingContainer = BindingContainerBuilder::create()
->bindImplementation(SomeInterface::class, SomeImplementation::class)
->build();
$injectableFactory = new InjectableFactory($container, $bindingContainer);
$obj = $injectableFactory->createResolved(SomeInterface::class);
$this->assertInstanceOf(SomeImplementation::class, $obj);
}
public function testCreateResolved2(): void
{
$container = $this->createMock(Container::class);
$bindingContainer = BindingContainerBuilder::create()->build();
$injectableFactory = new InjectableFactory($container, $bindingContainer);
$bindingContainer1 = BindingContainerBuilder::create()->build();
$obj = $injectableFactory->createResolved(SomeImplementation::class, $bindingContainer1);
$this->assertInstanceOf(SomeImplementation::class, $obj);
}
}

View File

@@ -0,0 +1,56 @@
<?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\Job;
use Espo\Core\Job\Job\Data;
class DataTest extends \PHPUnit\Framework\TestCase
{
public function testData1()
{
$data = Data
::create((object) ['test' => '1'])
->withTargetId('target-id')
->withTargetType('target-type');
$this->assertEquals('1', $data->get('test'));
$this->assertEquals('target-id', $data->getTargetId());
$this->assertEquals('target-type', $data->getTargetType());
}
public function testData2()
{
$data = Data
::create(['test' => '1']);
$this->assertEquals('1', $data->get('test'));
$this->assertEquals(null, $data->getTargetId());
}
}

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\Job;
use Espo\Core\Field\DateTime as DateTimeField;
use Espo\Core\Job\JobScheduler;
use Espo\Core\Job\JobScheduler\Creator;
use Espo\Core\Job\QueueName;
use Espo\Core\Job\Job\Data;
use PHPUnit\Framework\TestCase;
use tests\unit\testClasses\Core\Job\TestJob;
use DateTimeImmutable;
use DateInterval;
class JobSchedulerTest extends TestCase
{
private ?JobScheduler\Creator $creator = null;
protected function setUp(): void
{
$this->creator = $this->createMock(JobScheduler\Creator::class);
}
public function testSchedule1(): void
{
$scheduler = new JobScheduler($this->creator);
$time = new DateTimeImmutable();
$delay = DateInterval::createFromDateString('1 minute');
$expectedData = new Creator\Data(
className: TestJob::class,
queue: QueueName::Q0,
group: null,
data: new Data((object) ['test' => '1']),
time: DateTimeField::fromDateTime($time)->addMinutes(1),
);
$this->creator
->expects($this->once())
->method('create')
->with($expectedData);
$scheduler
->setClassName(TestJob::class)
->setQueue(QueueName::Q0)
->setData([
'test' => '1',
])
->setTime($time)
->setDelay($delay)
->schedule();
}
public function testSchedule2(): void
{
$scheduler = new JobScheduler($this->creator);
$time = new DateTimeImmutable();
$expectedData = new Creator\Data(
className: TestJob::class,
queue: null,
group: 'g-1',
data: (new Data((object) ['test' => '1']))->withTargetType('TestType')->withTargetId('test-id'),
time: DateTimeField::fromDateTime($time),
);
$this->creator
->expects($this->once())
->method('create')
->with($expectedData);
$data = Data
::create([
'test' => '1',
])
->withTargetId('test-id')
->withTargetType('TestType');
$scheduler
->setClassName(TestJob::class)
->setGroup('g-1')
->setData($data)
->setTime($time)
->schedule();
}
}

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\Job;
use Espo\Core\{
Job\MetadataProvider,
Utils\Metadata,
};
class MetadataProviderTest extends \PHPUnit\Framework\TestCase
{
private $metadata;
protected function setUp(): void
{
$this->metadata = $this->createMock(Metadata::class);
}
public function testGetPreparableJobNameList()
{
$this->metadata
->method('get')
->with(['app', 'scheduledJobs'])
->willReturn([
'Test1' => [
'preparatorClassName' => "Test",
],
'Test2' => [
'preparatorClassName' => "Test",
],
'Test3' => [],
'Test4' => [
'preparatorClassName' => null,
],
]);
$provider = new MetadataProvider($this->metadata);
$list = $provider->getPreparableJobNameList();
$this->assertEquals(['Test1', 'Test2'], $list);
}
}

View File

@@ -0,0 +1,76 @@
<?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\Job;
use Espo\Core\Job\QueueName;
use Espo\Core\Job\QueuePortionNumberProvider;
use Espo\Core\Utils\Config;
use PHPUnit\Framework\TestCase;
class QueuePortionNumberProviderTest extends TestCase
{
private $config;
protected function setUp(): void
{
$this->config = $this->createMock(Config::class);
}
public function testDefault(): void
{
$provider = new QueuePortionNumberProvider($this->config);
$this->assertEquals(200, $provider->get(QueueName::Q0));
$this->assertEquals(500, $provider->get(QueueName::Q1));
$this->assertEquals(100, $provider->get(QueueName::E0));
$this->assertEquals(200, $provider->get('TestDefault'));
}
public function testConfig(): void
{
$this->config
->method('get')
->willReturnMap(
[
['jobQ0MaxPortion', null, 201],
['jobQ1MaxPortion', null, 501],
['jobE0MaxPortion', null, 101],
['jobTestDefaultMaxPortion', null, 301]
]
);
$provider = new QueuePortionNumberProvider($this->config);
$this->assertEquals(201, $provider->get(QueueName::Q0));
$this->assertEquals(501, $provider->get(QueueName::Q1));
$this->assertEquals(101, $provider->get(QueueName::E0));
$this->assertEquals(301, $provider->get('TestDefault'));
}
}

View File

@@ -0,0 +1,255 @@
<?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\Job\QueueProcessor;
use Countable;
use Espo\Core\Job\QueueProcessor\Params;
use Espo\Core\Job\QueueProcessor\Picker;
use Espo\Core\Job\QueueUtil;
use Espo\Entities\Job;
use Espo\ORM\Collection;
use Espo\ORM\EntityCollection;
use PHPUnit\Framework\TestCase;
class PickerTest extends TestCase
{
/**
* @param string[] $ids
* @return Collection<Job>&Countable
*/
private function createCollection(array $ids): Collection&Countable
{
$output = new EntityCollection();
foreach ($ids as $id) {
$job = $this->createMock(Job::class);
$job->expects($this->any())
->method('getId')
->willReturn($id);
$output[] = $job;
}
return $output;
}
/**
* @param string[] $ids
* @param iterable<Job> $pick
*/
private function assertPick(array $ids, iterable $pick): void
{
$pick = iterator_to_array($pick);
$this->assertSameSize($ids, $pick);
foreach ($pick as $i => $job) {
$this->assertEquals($ids[$i], $job->getId());
}
}
/**
* @param float[] $weights
* @param string[][] $idsGroups
* @param string[] $idsExpected
*/
private function prepareAndTest(int $limit, array $weights, array $idsGroups, array $idsExpected): void
{
$util = $this->createMock(QueueUtil::class);
$params = Params::create()->withLimit($limit);
$paramList = [];
foreach ($weights as $i => $weight) {
$paramList[] = $params
->withWeight($weight)
->withQueue('m' . $i);
}
$params = $params->withSubQueueParamsList($paramList);
$picker = new Picker($util);
$collections = [];
foreach ($idsGroups as $ids) {
$collections[] = $this->createCollection($ids);
}
$util->expects($this->any())
->method('getPendingJobs')
->willReturnOnConsecutiveCalls(...$collections);
$pick = $picker->pick($params);
$this->assertPick($idsExpected, $pick);
}
public function testPick0(): void
{
$this->prepareAndTest(
limit: 10,
weights: [1.0],
idsGroups: [
['a0', 'a1', 'a2', 'a3', 'a4', 'a5'],
],
idsExpected:
['a0', 'a1', 'a2', 'a3', 'a4', 'a5'],
);
}
public function testPick1(): void
{
$this->prepareAndTest(
limit: 10,
weights: [0.5, 0.5],
idsGroups: [
['a0', 'a1', 'a2', 'a3', 'a4', 'a5'],
['b0', 'b1', 'b2', 'b3', 'b4', 'b5'],
],
idsExpected: array_merge(
['a0', 'a1', 'a2', 'a3', 'a4'],
['b0', 'b1', 'b2', 'b3', 'b4'],
),
);
}
public function testPick2(): void
{
$this->prepareAndTest(
limit: 10,
weights: [0.5, 0.5],
idsGroups: [
['a0', 'a1', 'a2'],
['b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7'],
],
idsExpected: array_merge(
['a0', 'a1', 'a2'],
['b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6'],
),
);
}
public function testPick3(): void
{
$this->prepareAndTest(
limit: 10,
weights: [0.5, 0.5],
idsGroups: [
['a0', 'a1', 'a2', 'a4', 'a5', 'a6', 'a7'],
['b0', 'b1', 'b2'],
],
idsExpected:
['a0', 'a1', 'a2', 'a4', 'a5', 'b0', 'b1', 'b2', 'a6', 'a7'],
);
}
public function testPick4(): void
{
$this->prepareAndTest(
limit: 10,
weights: [0.5, 0.5],
idsGroups: [
['a0', 'a1', 'a2'],
['b0', 'b1', 'b2'],
],
idsExpected:
['a0', 'a1', 'a2', 'b0', 'b1', 'b2'],
);
}
public function testPick5(): void
{
$this->prepareAndTest(
limit: 15,
weights: [0.5, 0.5],
idsGroups: [
['a0', 'a1', 'a2'],
['b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'b10', 'b11', 'b12'],
],
idsExpected: array_merge(
['a0', 'a1', 'a2'],
['b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'b10', 'b11'],
),
);
}
public function testPick6(): void
{
$this->prepareAndTest(
limit: 10,
weights: [0.5, 0.5],
idsGroups: [
[],
['b0', 'b1', 'b2'],
],
idsExpected:
[ 'b0', 'b1', 'b2'],
);
}
public function testPick7(): void
{
$this->prepareAndTest(
limit: 10,
weights: [0.5, 0.5],
idsGroups: [
['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6'],
['b0', 'b1', 'b2', 'b3', 'b4', 'b5'],
],
idsExpected: array_merge(
['a0', 'a1', 'a2', 'a3', 'a4'],
['b0', 'b1', 'b2', 'b3', 'b4'],
),
);
}
public function testPick8(): void
{
$this->prepareAndTest(
limit: 10,
weights: [0.5, 0.3, 0.2],
idsGroups: [
['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6'],
['b0', 'b1', 'b2', 'b3', 'b4', 'b5'],
['c0', 'c1', 'c2', 'c3', 'c4', 'c5'],
],
idsExpected: array_merge(
['a0', 'a1', 'a2', 'a3', 'a4'],
['b0', 'b1', 'b2'],
['c0', 'c1'],
),
);
}
}

View File

@@ -0,0 +1,62 @@
<?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\Job;
use Espo\Core\Job\QueueProcessor\Params;
use PHPUnit\Framework\TestCase;
class QueueProcessorParamsTest extends TestCase
{
protected function setUp() : void
{
}
public function testParams1()
{
$params = Params
::create()
->withLimit(10);
$this->assertEquals(10, $params->getLimit());
$this->assertNull($params->getQueue());
}
public function testParams2()
{
$params = Params
::create()
->withLimit(10)
->withGroup('group-0')
->withQueue('q0');
$this->assertEquals('q0', $params->getQueue());
$this->assertEquals('group-0', $params->getGroup());
}
}

View File

@@ -0,0 +1,123 @@
<?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\Log;
use Espo\Core\InjectableFactory;
use Espo\Core\Log\DefaultHandlerLoader;
use Espo\Core\Log\EspoRotatingFileHandlerLoader;
use Espo\Core\Log\Handler\EspoRotatingFileHandler;
use Espo\Core\Log\HandlerListLoader;
use PHPUnit\Framework\TestCase;
class HandlerListLoaderTest extends TestCase
{
private $injectableFactory;
protected function setUp() : void
{
$this->injectableFactory = $this->getMockBuilder(InjectableFactory::class)
->disableOriginalConstructor()
->getMock();
}
public function testLoad1()
{
$defaultLoader = $this->getMockBuilder(DefaultHandlerLoader::class)
->disableOriginalConstructor()
->getMock();
$listLoader = new HandlerListLoader($this->injectableFactory, $defaultLoader);
$dataList = [
[
'className' => 'Espo\\Core\\Log\\Handler\\EspoRotatingFileHandler',
'params' => [
'filename' => 'data/logs/test-1.log',
],
'level' => 'DEBUG',
'formatter' => [
'className' => 'Monolog\\Formatter\\LineFormatter',
'params' => [
'dateFormat' => 'Y-m-d H:i:s',
],
]
],
[
'loaderClassName' => EspoRotatingFileHandlerLoader::class,
'params' => [
'filename' => 'data/logs/test-2.log',
],
'level' => 'NOTICE',
],
];
$handler1 = $this->getMockBuilder(EspoRotatingFileHandler::class)
->disableOriginalConstructor()
->getMock();
$defaultLoader
->expects($this->once())
->method('load')
->with($dataList[0], 'NOTICE')
->willReturn($handler1);
$loader = $this->getMockBuilder(EspoRotatingFileHandlerLoader::class)
->disableOriginalConstructor()
->getMock();
$handler = $this->getMockBuilder(EspoRotatingFileHandler::class)
->disableOriginalConstructor()
->getMock();
$this->injectableFactory
->expects($this->once())
->method('create')
->with(EspoRotatingFileHandlerLoader::class)
->willReturn($loader);
$params = [
'filename' => 'data/logs/test-2.log',
'level' => 'NOTICE',
];
$loader
->expects($this->once())
->method('load')
->with($params)
->willReturn($handler);
$list = $listLoader->load($dataList, 'NOTICE');
$this->assertCount(2, $list);
$this->assertInstanceOf(EspoRotatingFileHandler::class, $list[0]);
}
}

View File

@@ -0,0 +1,107 @@
<?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\Mail\Account;
use Espo\Core\Mail\Account\GroupAccount\BouncedRecognizer;
use Espo\Core\Mail\MessageWrapper;
use Espo\Core\Mail\Parsers\MailMimeParser;
use Espo\ORM\EntityManager;
class BouncedRecognizerTest extends \PHPUnit\Framework\TestCase
{
protected BouncedRecognizer $bouncedRecognizer;
protected function setUp(): void
{
$this->bouncedRecognizer = new BouncedRecognizer();
}
private function createMessage(string $contents): MessageWrapper
{
$entityManager = $this->createMock(EntityManager::class);
$parser = new MailMimeParser($entityManager);
return new MessageWrapper(0, null, $parser, $contents);
}
public function testBounced1a(): void
{
$contents = file_get_contents('tests/unit/testData/Core/Mail/bounced_1.eml');
$message = $this->createMessage($contents);
$this->assertTrue($this->bouncedRecognizer->isBounced($message));
$this->assertTrue($this->bouncedRecognizer->isHard($message));
$this->assertEquals('0011aa', $this->bouncedRecognizer->extractQueueItemId($message));
}
public function testBounced1b(): void
{
$contents = file_get_contents('tests/unit/testData/Core/Mail/bounced_1.eml');
$contents = str_replace('MAILER-DAEMON', 'test', $contents);
$message = $this->createMessage($contents);
$this->assertTrue($this->bouncedRecognizer->isBounced($message));
$this->assertTrue($this->bouncedRecognizer->isHard($message));
}
public function testBounced2(): void
{
$contents = file_get_contents('tests/unit/testData/Core/Mail/bounced_2.eml');
$message = $this->createMessage($contents);
$this->assertTrue($this->bouncedRecognizer->isBounced($message));
$this->assertFalse($this->bouncedRecognizer->isHard($message));
}
public function testNotBounced1(): void
{
$contents = file_get_contents('tests/unit/testData/Core/Mail/test_email_1.eml');
$message = $this->createMessage($contents);
$this->assertFalse($this->bouncedRecognizer->isBounced($message));
}
public function testBounced3(): void
{
$contents = file_get_contents('tests/unit/testData/Core/Mail/bounced_3.eml');
$message = $this->createMessage($contents);
$this->assertTrue($this->bouncedRecognizer->isBounced($message));
$this->assertTrue($this->bouncedRecognizer->isHard($message));
$this->assertEquals('5.4.1', $this->bouncedRecognizer->extractStatus($message));
}
}

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\unit\Espo\Core\Mail\Account;
use Espo\Core\Mail\Account\FetchData;
use Espo\Core\Field\DateTime;
class FetchDataTest extends \PHPUnit\Framework\TestCase
{
public function testGetSet(): void
{
$raw = (object) [
'lastUID' => (object) [
'test' => '10',
],
'lastDate' => (object) [
'test' => '2022-01-01 00:00:00',
],
];
$data = FetchData::fromRaw($raw, 0);
$this->assertEquals(10, $data->getLastUid('test'));
$this->assertEquals('2022-01-01 00:00:00', $data->getLastDate('test')->toString());
$this->assertEquals(null, $data->getLastUid('not-existing'));
$this->assertEquals(null, $data->getLastDate('not-existing'));
$this->assertEquals(false, $data->getForceByDate('test'));
$this->assertEquals($raw, $data->getRaw());
$now = DateTime::createNow();
$data->setForceByDate('test', true);
$data->setLastUid('test', 11);
$data->setLastDate('test', $now);
$this->assertEquals(11, $data->getLastUid('test'));
$this->assertEquals(true, $data->getForceByDate('test'));
$this->assertEquals($now, $data->getLastDate('test'));
}
}

View File

@@ -0,0 +1,298 @@
<?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\Mail\Account;
use Espo\Core\Field\Date;
use Espo\Core\Field\DateTime;
use Espo\Core\Mail\Account\Account;
use Espo\Core\Mail\Account\FetchData;
use Espo\Core\Mail\Account\Fetcher;
use Espo\Core\Mail\Account\Fetcher\ConfigDataProvider;
use Espo\Core\Mail\Account\Storage;
use Espo\Core\Mail\Account\StorageFactory;
use Espo\Core\Mail\Importer;
use Espo\Core\Mail\ParserFactory;
use Espo\Core\Utils\Log;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class FetcherTest extends TestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testFetchSinceDate(): void
{
$storage = $this->createMock(Storage::class);
$importer = $this->createMock(Importer::class);
$fetcher = $this->prepareFetcher($storage, $importer);
$fetchData = $this->prepareFetchData(
lastUid: null,
lastDate: null,
uidValidity: null,
);
$account = $this->prepareAccount($fetchData);
$storage
->expects($this->once())
->method('selectFolder')
->with('INBOX');
$storage
->expects($this->once())
->method('getFolderStatus')
->willReturn(new Storage\FolderStatus(uidValidity: 1));
$storage
->expects($this->once())
->method('getUidsSinceDate')
->with(DateTime::fromString('2026-01-01 00:00'))
->willReturn([1, 2, 3]);
$storage
->expects($this->once())
->method('close');
$importer
->expects($this->exactly(3))
->method('import');
$fetchData
->expects($this->once())
->method('setUidValidity')
->with('INBOX', 1);
$fetchData
->expects($this->once())
->method('setLastUid')
->with('INBOX', 3);
$fetcher->fetch($account);
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testFetchSinceLastUid(): void
{
$storage = $this->createMock(Storage::class);
$importer = $this->createMock(Importer::class);
$fetcher = $this->prepareFetcher($storage, $importer);
$fetchData = $this->prepareFetchData(
lastUid: 3,
lastDate: DateTime::fromString('2026-01-02 00:00'),
uidValidity: 1,
);
$account = $this->prepareAccount($fetchData);
$storage
->expects($this->once())
->method('selectFolder')
->with('INBOX');
$storage
->expects($this->once())
->method('getFolderStatus')
->willReturn(new Storage\FolderStatus(uidValidity: 1));
$storage
->expects($this->once())
->method('getUidsFromUid')
->with(3)
->willReturn([4]);
$storage
->expects($this->once())
->method('close');
$importer
->expects($this->exactly(1))
->method('import');
$fetchData
->expects($this->once())
->method('setLastUid')
->with('INBOX', 4);
$fetcher->fetch($account);
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testFetchUidValidityBroken(): void
{
$storage = $this->createMock(Storage::class);
$importer = $this->createMock(Importer::class);
$fetcher = $this->prepareFetcher($storage, $importer);
$fetchData = $this->prepareFetchData(
lastUid: 3,
lastDate: DateTime::fromString('2026-01-02 00:00'),
uidValidity: 1,
);
$account = $this->prepareAccount($fetchData);
$storage
->expects($this->once())
->method('selectFolder')
->with('INBOX');
$storage
->expects($this->once())
->method('getFolderStatus')
->willReturn(new Storage\FolderStatus(uidValidity: 2));
$storage
->expects($this->once())
->method('getUidsSinceDate')
->with(DateTime::fromString('2026-01-02 00:00'))
->willReturn([4]);
$storage
->expects($this->once())
->method('close');
$importer
->expects($this->exactly(1))
->method('import');
$fetchData
->expects($this->once())
->method('setUidValidity')
->with('INBOX', 2);
$fetchData
->expects($this->once())
->method('setLastUid')
->with('INBOX', 4);
$fetcher->fetch($account);
}
private function prepareFetchData(
?int $lastUid,
?DateTime $lastDate,
?int $uidValidity,
): FetchData & MockObject {
$fetchData = $this->createMock(FetchData::class);
$fetchData
->method('getLastUid')
->willReturn($lastUid);
$fetchData
->method('getLastDate')
->willReturn($lastDate);
$fetchData
->method('getUidValidity')
->willReturn($uidValidity);
$fetchData
->method('getForceByDate')
->willReturn(false);
return $fetchData;
}
private function prepareAccount(FetchData $fetchData): Account & MockObject
{
$account = $this->createMock(Account::class);
$account
->method('isAvailableForFetching')
->willReturn(true);
$account
->method('getMonitoredFolderList')
->willReturn(['INBOX']);
$account
->expects($this->once())
->method('getPortionLimit')
->willReturn(5);
$account
->expects($this->once())
->method('getFetchData')
->willReturn($fetchData);
$account
->method('getFetchSince')
->willReturn(Date::fromString('2026-01-01'));
$account
->expects($this->once())
->method('updateFetchData')
->with($fetchData);
return $account;
}
private function prepareFetcher(
Storage & MockObject $storage,
Importer & MockObject $importer,
): Fetcher {
$storageFactory = $this->createMock(StorageFactory::class);
$configDataProvider = $this->createMock(ConfigDataProvider::class);
$log = $this->createMock(Log::class);
$parserFactory = $this->createMock(ParserFactory::class);
$filtersProvider = $this->createMock(Fetcher\FiltersProvider::class);
$unlocker = $this->createMock(Fetcher\Unlocker::class);
$messageFactory = $this->createMock(Fetcher\MessageFactory::class);
$storageFactory
->expects($this->once())
->method('create')
->willReturn($storage);
return new Fetcher(
importer: $importer,
storageFactory: $storageFactory,
configDataProvider: $configDataProvider,
log: $log,
parserFactory: $parserFactory,
filtersProvider: $filtersProvider,
unlocker: $unlocker,
messageFactory: $messageFactory,
);
}
}

View File

@@ -0,0 +1,227 @@
<?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\Mail\Event;
use ICal\ICal;
use ICal\Event;
use Espo\Core\Mail\Event\EventFactory;
use Espo\Core\Mail\Event\Event as MailEvent;
class EventTest extends \PHPUnit\Framework\TestCase
{
private $icsContents1 =
"BEGIN:VCALENDAR
METHOD:REQUEST
PRODID:Microsoft Exchange Server 2010
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Jordan Standard Time
BEGIN:STANDARD
DTSTART:16010101T010000
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1FR;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T235959
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1TH;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
ORGANIZER;CN=Test Org:MAILTO:test-org@test.com
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Test-1:MAIL
TO:test-1@test.com
ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Test-2:MAILTO:test-2@test.com
UID:040000008200E00074C5B7101A82E008000000004679AD350342D501000000000000000
010000000BB4B99DDF6B4934B8DC8B8BA92CF3645
SUMMARY;LANGUAGE=en-US:test2
DTSTART;TZID=Jordan Standard Time:20190729T180000
DTEND;TZID=Jordan Standard Time:20190729T183000
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20190724T093603Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION;LANGUAGE=en-US:
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-OWNERAPPTID:2117584454
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:0
X-MICROSOFT-DONOTFORWARDMEETING:FALSE
X-MICROSOFT-DISALLOW-COUNTER:FALSE
X-MICROSOFT-LOCATIONS:[]
BEGIN:VALARM
DESCRIPTION:REMINDER
TRIGGER;RELATED=START:-PT15M
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR
";
private $icsContents2 =
"BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
SUMMARY:Test-2a
DTSTART;TZID=America/New_York:20130802T103400
DTEND;TZID=America/New_York:20130802T110400
LOCATION:1 Broadway Ave.\, Brooklyn
DESCRIPTION: Description Test 1
STATUS:CONFIRMED
SEQUENCE:3
BEGIN:VALARM
TRIGGER:-PT10M
DESCRIPTION:Pickup Reminder
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR
";
private $icsContents3 =
"BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
DTSTART;VALUE=DATE:20210810
DTEND;VALUE=DATE:20210811
DTSTAMP:20210810T091857Z
ORGANIZER;CN=test:mailto:test@group.calendar.google.c
om
UID:4r15namb5v2h4dou58gkfajjbe@google.com
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=
TRUE;CN=test.com;X-NUM-GUESTS=0:mailto:test@test.com
X-MICROSOFT-CDO-OWNERAPPTID:1443094082
CREATED:20210810T091748Z
LAST-MODIFIED:20210810T091856Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:test ics 4
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR";
public function testEvent1(): void
{
$ical = new ICal();
$ical->initString($this->icsContents1);
/* @var $event Event */
$event = $ical->events()[0];
$espoEvent = MailEvent::create()
->withUid($event->uid ?? null)
->withDateStart($event->dtstart_tz ?? null)
->withDateEnd($event->dtend_tz ?? null)
->withName($event->summary ?? null)
->withLocation($event->location ?? null)
->withDescription($event->description ?? null)
->withTimezone($ical->calendarTimeZone() ?? null)
->withOrganizer($event->organizer)
->withAttendees($event->attendee);
$this->assertEquals(
'040000008200E00074C5B7101A82E008000000004679AD350342D501000000000000000' .
'010000000BB4B99DDF6B4934B8DC8B8BA92CF3645',
$espoEvent->getUid()
);
$this->assertEquals('2019-07-29 15:00:00', $espoEvent->getDateStart());
$this->assertEquals('test-org@test.com', $espoEvent->getOrganizerEmailAddress());
$this->assertEquals(['test-1@test.com', 'test-2@test.com'], $espoEvent->getAttendeeEmailAddressList());
}
public function testEvent2(): void
{
$ical = new ICal();
$ical->initString($this->icsContents2);
/* @var $event Event */
$event = $ical->events()[0];
$espoEvent = MailEvent::create()
->withUid($event->uid ?? null)
->withDateStart($event->dtstart_tz ?? null)
->withDateEnd($event->dtend_tz ?? null)
->withName($event->summary ?? null)
->withLocation($event->location ?? null)
->withDescription($event->description ?? null)
->withTimezone($ical->calendarTimeZone() ?? null)
->withOrganizer($event->organizer)
->withAttendees($event->attendee);
$this->assertEquals(
null,
$espoEvent->getUid()
);
$this->assertEquals('2013-08-02 14:34:00', $espoEvent->getDateStart());
$this->assertEquals(null, $espoEvent->getOrganizerEmailAddress());
$this->assertEquals([], $espoEvent->getAttendeeEmailAddressList());
$this->assertEquals(
"1 Broadway Ave., Brooklyn",
$espoEvent->getLocation()
);
$this->assertFalse($espoEvent->isAllDay());
}
public function testEvent3(): void
{
$ical = new ICal();
$ical->initString($this->icsContents3);
$event = EventFactory::createFromU01jmg3Ical($ical);
$this->assertTrue($event->isAllDay());
$this->assertEquals('2021-08-10', $event->getDateStart());
$this->assertEquals('2021-08-10', $event->getDateEnd());
}
}

View File

@@ -0,0 +1,295 @@
<?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\Mail;
use Espo\Entities\Email;
use Espo\Entities\EmailFilter;
use Espo\Core\Mail\FiltersMatcher;
use Espo\ORM\EntityManager;
use PHPUnit\Framework\TestCase;
class FiltersMatcherTest extends TestCase
{
private $object;
private $entityManager;
private $emailDefs;
private $filterDefs;
protected function setUp() : void
{
$this->object = new FiltersMatcher();
$this->entityManager = $this->createMock(EntityManager::class);
$this->emailDefs = [
'attributes' => [
'from' => [
'type' => 'varchar'
],
'to' => [
'type' => 'varchar'
],
'name' => [
'type' => 'varchar'
],
'subject' => [
'type' => 'varchar'
],
'body' => [
'type' => 'text'
],
'bodyPlain' => [
'type' => 'text'
]
]
];
$this->filterDefs = [
'attributes' => [
'from' => [
'type' => 'varchar'
],
'to' => [
'type' => 'varchar'
],
'subject' => [
'type' => 'varchar'
],
'bodyContains' => [
'type' => 'jsonArray'
],
'bodyContainsAll' => [
'type' => 'jsonArray'
],
]
];
}
protected function tearDown() : void
{
$this->object = NULL;
}
protected function createEntity(string $entityType, string $className, array $defs)
{
return new $className($entityType, $defs, $this->entityManager);
}
public function testMatch(): void
{
$email = $this->createEntity('Email', Email::class, $this->emailDefs);
$email->set('from', 'test@tester');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'from' => 'test@tester'
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
$email = $this->createEntity('Email', Email::class, $this->emailDefs);
$email->set('from', 'test@tester');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'from' => '*@tester'
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
$email->set('from', 'test@tester');
$email->set('to', 'test@tester;baraka@tester');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'to' => 'baraka@tester'
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
$email->set('from', 'test@tester');
$email->set('to', 'test@tester;baraka@man');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'to' => '*@tester'
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
$email->set('subject', 'test hello man');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'subject' => '*hello*'
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
$email->set('name', 'test hello man');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'subject' => 'hello'
]);
$filterList = [$filter];
$this->assertNull($this->object->findMatch($email, $filterList));
$email->set('name', 'test hello man');
$email->set('from', 'test@tester');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'subject' => '*hello*',
'from' => 'test@tester'
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
$email->set('name', 'test hello man');
$email->set('from', 'hello@tester');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'subject' => '*hello*',
'from' => 'test@tester'
]);
$filterList = [$filter];
$this->assertNull($this->object->findMatch($email, $filterList));
$email->set('name', 'test hello man');
$email->set('body', 'one hello three');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'subject' => 'test hello man',
'bodyContains' => ['hello']
]);
$filterList = [$filter];
$this->assertNull($this->object->findMatch($email, $filterList, true));
$email->set('name', 'test hello man');
$email->set('body', 'one hello three');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'subject' => 'test hello man',
'bodyContains' => ['hello']
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
$email->set('name', 'Access information to the EspoCRM cloud');
$email->set('from', 'no-reply@test.com');
$email->set('to', 'info@test.com');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'subject' => 'Access information to the EspoCRM cloud',
'from' => 'no-reply@test.com',
'to' => 'info@test.com'
]);
$this->assertTrue($this->object->match($email, $filter));
$email->set('body', 'test hello');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'bodyContainsAll' => ['test', 'hello'],
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
$email->set('body', 'test');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'bodyContainsAll' => ['test', 'hello'],
]);
$filterList = [$filter];
$this->assertNull($this->object->findMatch($email, $filterList));
$email->set('body', 'test hello one');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'bodyContains' => ['test', 'one'],
'bodyContainsAll' => ['test', 'hello'],
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
}
public function testMatchBody()
{
$email = $this->createEntity('Email', Email::class, $this->emailDefs);
$email->set('body', 'hello Man tester');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'bodyContains' => ['man', 'red']
]);
$filterList = [$filter];
$this->assertNotNull($this->object->findMatch($email, $filterList));
$email = $this->createEntity('Email', Email::class, $this->emailDefs);
$email->set('body', 'hello Man tester');
$email->set('from', 'hello@test');
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$filter->set([
'bodyContains' => ['man', 'red'],
'from' => 'test@tester'
]);
$filterList = [$filter];
$this->assertNull($this->object->findMatch($email, $filterList));
}
public function testMatchEmpty(): void
{
$email = $this->createEntity('Email', Email::class, $this->emailDefs);
$filter = $this->createEntity('EmailFilter', EmailFilter::class, $this->filterDefs);
$email->set([
'name' => 'Test',
'from' => 'test@test.com',
'to' => 'test@test.com',
'body' => 'Test',
]);
$filter->set([
'subject' => '',
'bodyContains' => [''],
'from' => '',
'to' => '',
]);
$this->assertNull($this->object->findMatch($email, [$filter]));
}
}

View File

@@ -0,0 +1,73 @@
<?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\Mail;
use Espo\Core\Mail\Importer\Data as ImporterData;
use Espo\Entities\EmailFilter;
class ImporterDataTest extends \PHPUnit\Framework\TestCase
{
function testData1()
{
$filter = $this->createMock(EmailFilter::class);
$data = ImporterData
::create()
->withTeamIdList(['t1'])
->withUserIdList(['u1'])
->withAssignedUserId('a1')
->withFetchOnlyHeader(true)
->withFolderData(['t' => '1'])
->withFilterList([$filter]);
$this->assertEquals(['t1'], $data->getTeamIdList());
$this->assertEquals(['u1'], $data->getUserIdList());
$this->assertEquals('a1', $data->getAssignedUserId());
$this->assertEquals(true, $data->fetchOnlyHeader());
$this->assertEquals(['t' => '1'], $data->getFolderData());
$this->assertEquals([$filter], $data->getFilterList());
}
function testData2()
{
$data = ImporterData
::create()
->withFetchOnlyHeader(false);
$this->assertEquals([], $data->getTeamIdList());
$this->assertEquals([], $data->getUserIdList());
$this->assertEquals(null, $data->getAssignedUserId());
$this->assertEquals(false, $data->fetchOnlyHeader());
$this->assertEquals([], $data->getFilterList());
}
}

View File

@@ -0,0 +1,236 @@
<?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\Mail;
use Espo\Entities\Email;
use Espo\Core\Notification\AssignmentNotificator;
use Espo\ORM\Metadata;
use Espo\ORM\Value\ValueAccessor;
use Espo\ORM\Value\ValueAccessorFactory;
use Espo\Core\FieldProcessing\Relation\LinkMultipleSaver;
use Espo\Core\Job\JobSchedulerFactory;
use Espo\Core\Mail\Importer;
use Espo\Core\Mail\Importer\Data as ImporterData;
use Espo\Core\Mail\MessageWrapper;
use Espo\Core\Mail\ParserFactory;
use Espo\Core\Mail\Parsers\MailMimeParser;
use Espo\Core\Notification\AssignmentNotificatorFactory;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Repositories\Database;
use Espo\Core\Utils\Config;
use Espo\ORM\Repository\RDBSelectBuilder;
use PHPUnit\Framework\TestCase;
class ImporterTest extends TestCase
{
private $emailRepository;
private $config;
private $assignmentNotificatorFactory;
private $parserFactory;
private $linkMultipleSaver;
private $parentFinder;
private $jobSchedulerFactory;
private $duplicateFinder;
private $email;
private $repositoryMap;
private $entityManager;
protected function setUp(): void
{
$entityManager = $this->entityManager = $this->createMock(EntityManager::class);
$this->config = $this->createMock(Config::class);
$emailRepository = $this->createMock(Database::class);
$emptyRepository = $this->createMock(Database::class);
$metadata = $this->createMock(Metadata::class);
$selectBuilder = $this->createMock(RDBSelectBuilder::class);
$this->assignmentNotificatorFactory = $this->createMock(AssignmentNotificatorFactory::class);
$this->parserFactory = $this->createMock(ParserFactory::class);
$this->linkMultipleSaver = $this->createMock(LinkMultipleSaver::class);
$this->parentFinder = $this->createMock(Importer\ParentFinder::class);
$this->parserFactory
->expects($this->any())
->method('create')
->willReturnCallback(
function () {
return new MailMimeParser($this->entityManager);
}
);
$emailRepository
->expects($this->any())
->method('where')
->willReturn($selectBuilder);
$emailRepository
->expects($this->any())
->method('select')
->willReturn($selectBuilder);
$entityManager
->expects($this->any())
->method('getMetadata')
->willReturn($metadata);
$metadata
->expects($this->any())
->method('get')
->willReturn(null);
$emptyRepository
->expects($this->any())
->method('where')
->willReturn($selectBuilder);
$this->emailRepository = $emailRepository;
$this->repositoryMap = [
[Email::class, $this->emailRepository],
//['Account', $emptyRepository],
//['Contact', $emptyRepository],
//['Lead', $emptyRepository],
];
$valueAccessor = $this->createMock(ValueAccessor::class);
$valueAccessorFactory = $this->createMock(ValueAccessorFactory::class);
$valueAccessorFactory
->expects($this->any())
->method('create')
->willReturn(
$valueAccessor
);
$emailDefs = require('tests/unit/testData/Core/Mail/email_defs.php');
//$attachmentDefs = require('tests/unit/testData/Core/Mail/attachment_defs.php');
$this->email = new Email('Email', $emailDefs, $entityManager, $valueAccessorFactory);
//$attachment = new Attachment('Attachment', $attachmentDefs, $entityManager, $valueAccessorFactory);
$this->assignmentNotificatorFactory
->expects($this->any())
->method('create')
->willReturn(
$this->createMock(AssignmentNotificator::class)
);
$this->duplicateFinder = $this->createMock(Importer\DuplicateFinder::class);
$this->jobSchedulerFactory = $this->createMock(JobSchedulerFactory::class);
}
public function testImport1(): void
{
$entityManager = $this->entityManager;
$config = $this->config;
$email = $this->email;
$entityManager
->expects($this->any())
->method('getRDBRepositoryByClass')
->willReturnMap($this->repositoryMap);
$entityManager
->expects($this->exactly(1))
->method('saveEntity')
->with($this->isInstanceOf(Email::class))
->willReturnCallback(function (Email $entity) {
$entity->set('id', 'test-id');
});
$this->emailRepository
->expects($this->once())
->method('getNew')
->willReturn($email);
$config
->expects($this->any())
->method('get')
->willReturnMap(
[
['b2cMode', false]
]
);
$saver = $this->createMock(Importer\EmailSaver::class);
$saver
->expects($this->exactly(1))
->method('save')
->with($this->isInstanceOf(Email::class));
$contents = file_get_contents('tests/unit/testData/Core/Mail/test_email_1.eml');
$importer = new Importer\DefaultImporter(
entityManager: $entityManager,
config: $config,
notificatorFactory: $this->assignmentNotificatorFactory,
parserFactory: $this->parserFactory,
linkMultipleSaver: $this->linkMultipleSaver,
duplicateFinder: $this->duplicateFinder,
jobSchedulerFactory: $this->jobSchedulerFactory,
parentFinder: $this->parentFinder,
autoReplyDetector: $this->createMock(Importer\AutoReplyDetector::class),
emailSaver: $saver,
);
$message = new MessageWrapper(0, null, null, $contents);
$data = ImporterData
::create()
->withTeamIdList(['teamTestId'])
->withUserIdList(['userTestId']);
$email = $importer->import($message, $data);
$this->assertEquals('test 3', $email->get('name'));
$userIdList = $email->getLinkMultipleIdList('users');
$this->assertTrue(in_array('userTestId', $userIdList));
$this->assertStringContainsString('<br>Admin Test', $email->get('body'));
$this->assertStringContainsString('Admin Test', $email->get('bodyPlain'));
$messageId = '<e558c4dfc2a0f0d60f5ebff474c97ffc/1466410740/1950@espo>';
$this->assertEquals($messageId, $email->get('messageId'));
$this->assertEquals($messageId . '-r@test.com', $email->get('messageIdInternal'));
}
}

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\Mail;
use Espo\Core\Mail\SenderParams;
class SenderParamsTest extends \PHPUnit\Framework\TestCase
{
public function testFromArray1(): void
{
$array = [
'fromAddress' => 'test@test',
'fromName' => 'name',
'replyToAddress' => 'reply@test',
'replyToName' => 'reply',
];
$this->assertEquals($array, SenderParams::fromArray($array)->toArray());
}
public function testFromArray2(): void
{
$array = [
'fromAddress' => 'test@test',
'fromName' => 'name',
];
$this->assertEquals($array, SenderParams::fromArray($array)->toArray());
}
public function testBuilding(): void
{
$params = SenderParams::create()
->withFromAddress('test@test')
->withFromName('name')
->withReplyToAddress('r@test')
->withReplyToName('r');
$this->assertEquals('test@test', $params->getFromAddress());
$this->assertEquals('name', $params->getFromName());
$this->assertEquals('r@test', $params->getReplyToAddress());
$this->assertEquals('r', $params->getReplyToName());
}
}

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\Mail;
use Espo\Core\Mail\SmtpParams;
use PHPUnit\Framework\TestCase;
class SmtpParamsTest extends TestCase
{
public function testFromArray1(): void
{
$array = [
'server' => 'localhost',
'port' => 587,
'fromAddress' => 'test@test',
'fromName' => 'name',
'auth' => false,
];
$this->assertEquals($array, SmtpParams::fromArray($array)->toArray());
}
public function testFromArray2(): void
{
$array = [
'server' => 'localhost',
'port' => 587,
'fromAddress' => 'test@test',
'fromName' => 'name',
'auth' => true,
'connectionOptions' => ['test' => 'test'],
'authMechanism' => 'login',
'username' => 'tester',
'password' => 'password',
'security' => 'ssl',
];
$this->assertEquals($array, SmtpParams::fromArray($array)->toArray());
}
public function testBuilding(): void
{
$params = SmtpParams::create('localhost', 587)
->withFromAddress('test@test')
->withFromName('name')
->withAuth()
->withUsername('tester')
->withPassword('test')
->withAuthMechanism('login')
->withConnectionOptions(['test' => 'test']);
$this->assertEquals('localhost', $params->getServer());
$this->assertEquals(587, $params->getPort());
$this->assertEquals('test@test', $params->getFromAddress());
$this->assertEquals('name', $params->getFromName());
$this->assertEquals(true, $params->useAuth());
$this->assertEquals('tester', $params->getUsername());
$this->assertEquals('test', $params->getPassword());
$this->assertEquals(['test' => 'test'], $params->getConnectionOptions());
}
}

View File

@@ -0,0 +1,60 @@
<?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\MassAction;
use Espo\Core\{
MassAction\Data,
};
class DataTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
}
public function testData()
{
$original = (object) [
'test' => 'hello',
];
$data = Data::fromRaw($original);
$this->assertEquals('hello', $data->get('test'));
$this->assertTrue($data->has('test'));
$this->assertFalse($data->has('no'));
$this->assertEquals($data->getRaw(), $original);
$this->assertNotSame($data->getRaw(), $original);
}
}

View File

@@ -0,0 +1,181 @@
<?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\MassAction;
use Espo\Core\{
MassAction\Params,
Select\SearchParams,
};
use RuntimeException;
class ParamsTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
}
public function testFromRawIds()
{
$params = Params::fromRaw(
[
'ids' => ['1'],
'entityType' => 'Test',
]
);
$this->assertEquals('Test', $params->getEntityType());
$this->assertEquals(['1'], $params->getIds());
$this->assertTrue($params->hasIds());
}
public function testFromIds()
{
$params = Params::createWithIds('Test', ['1']);
$this->assertEquals('Test', $params->getEntityType());
$this->assertEquals(['1'], $params->getIds());
}
public function testFromSearchParams()
{
$searchParams = $this->createMock(SearchParams::class);
$params = Params::createWithSearchParams('Test', $searchParams);
$this->assertEquals('Test', $params->getEntityType());
$this->assertEquals($searchParams, $params->getSearchParams());
}
public function testFromRawSearchParams1()
{
$where = [
[
'type' => 'equals',
'attribute' => 'name',
'value' => 'test',
]
];
$params = Params::fromRaw(
[
'where' => $where,
'searchParams' => [
'primaryFilter' => 'testFilter',
],
],
'Test'
);
$searchParams = $params->getSearchParams();
$this->assertEquals('Test', $params->getEntityType());
$this->assertEquals($where, $searchParams->getWhere()->getRaw()['value']);
$this->assertEquals('testFilter', $searchParams->getPrimaryFilter());
$this->assertFalse($params->hasIds());
}
public function testFromRawSearchParams2()
{
$where = [
[
'type' => 'equals',
'attribute' => 'name',
'value' => 'test',
]
];
$params = Params::fromRaw(
[
'searchParams' => [
'primaryFilter' => 'testFilter',
'where' => $where,
],
],
'Test'
);
$searchParams = $params->getSearchParams();
$this->assertEquals($where, $searchParams->getWhere()->getRaw()['value']);
$this->assertEquals('testFilter', $searchParams->getPrimaryFilter());
$this->assertFalse($params->hasIds());
}
public function testFromRawSearchException1()
{
$this->expectException(RuntimeException::class);
Params::fromRaw(
[
'ids' => ['1'],
'searchParams' => [
'primaryFilter' => 'testFilter',
'where' => [],
],
],
'Test'
);
}
public function testSerialize1(): void
{
$params1 = Params::fromRaw(
[
'searchParams' => [
'primaryFilter' => 'testFilter',
'where' => [
[
'type' => 'equals',
'attribute' => 'name',
'value' => 'test',
]
],
],
],
'Test'
);
$params2 = Params::fromSerializedRaw(base64_encode(serialize($params1)));
$this->assertEquals($params1, $params2);
$this->assertEquals('equals', $params2->getSearchParams()->getWhere()->getItemList()[0]->getType());
}
}

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\Notification;
use Espo\Core\Notification\AssignmentNotificator\Params;
use Espo\Core\ORM\Repository\Option\SaveOption;
class AssignmentNotificatorParamsTest extends \PHPUnit\Framework\TestCase
{
public function testOne(): void
{
$options = [SaveOption::SILENT => true];
$params = Params
::create()
->withRawOptions($options);
$this->assertEquals(true, $params->getOption('silent'));
$this->assertEquals(true, $params->hasOption('silent'));
$this->assertEquals(null, $params->getOption('skipHooks'));
$this->assertEquals(false, $params->hasOption('skipHooks'));
$this->assertEquals($options, $params->getRawOptions());
}
}

View File

@@ -0,0 +1,89 @@
<?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\ORM;
use Espo\Core\ORM\DatabaseParamsFactory;
use Espo\Core\Utils\Config;
class DatabaseParamsFactoryTest extends \PHPUnit\Framework\TestCase
{
public function testCreate(): void
{
$factory = new DatabaseParamsFactory($this->createConfig());
$params = $factory->create();
$this->assertEquals('test:host', $params->getHost());
$this->assertEquals(10, $params->getPort());
$this->assertEquals('name-db', $params->getName());
$this->assertEquals('test-user', $params->getUsername());
$this->assertEquals('test-password', $params->getPassword());
$this->assertEquals('test-platform', $params->getPlatform());
}
public function testCreateWithMergedAssoc(): void
{
$factory = new DatabaseParamsFactory($this->createConfig());
$params = $factory->createWithMergedAssoc([
'host' => 'test:host2',
'port' => 11,
'dbname' => 'name2-db',
'user' => 'test-user2',
'password' => 'test-password2',
]);
$this->assertEquals('test:host2', $params->getHost());
$this->assertEquals(11, $params->getPort());
$this->assertEquals('name2-db', $params->getName());
$this->assertEquals('test-user2', $params->getUsername());
$this->assertEquals('test-password2', $params->getPassword());
$this->assertEquals('test-platform', $params->getPlatform());
}
private function createConfig(): Config
{
$config = $this->createMock(Config::class);
$config->expects($this->any())
->method('get')
->willReturnMap([
['database', null, ['d' => 'd']],
['database.host', null, 'test:host'],
['database.port', null, 10],
['database.dbname', null, 'name-db'],
['database.user', null, 'test-user'],
['database.password', null, 'test-password'],
['database.platform', null, 'test-platform'],
]);
return $config;
}
}

View File

@@ -0,0 +1,109 @@
<?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\Portal\Acl;
use Espo\Core\{
Portal\Acl\AccessChecker\ScopeCheckerData,
};
class ScopeCheckerDataTest extends \PHPUnit\Framework\TestCase
{
protected function setUp(): void
{
}
public function testCheckerData0()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$this->assertEquals(false, $checkerData->isOwn());
$this->assertEquals(false, $checkerData->inAccount());
$this->assertEquals(false, $checkerData->inContact());
}
public function testCheckerData1()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInAccount(true)
->setInContact(true)
->build();
$this->assertEquals(true, $checkerData->isOwn());
$this->assertEquals(true, $checkerData->inAccount());
$this->assertEquals(true, $checkerData->inContact());
}
public function testCheckerData2()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(false)
->setInAccountChecker(
function (): bool {
return true;
}
)
->build();
$this->assertEquals(false, $checkerData->isOwn());
$this->assertEquals(true, $checkerData->inAccount());
}
public function testCheckerData3()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwnChecker(
function (): bool {
return false;
}
)
->setInAccountChecker(
function (): bool {
return false;
}
)
->setInContactChecker(
function (): bool {
return true;
}
)
->build();
$this->assertEquals(false, $checkerData->isOwn());
$this->assertEquals(false, $checkerData->inAccount());
$this->assertEquals(true, $checkerData->inContact());
}
}

View File

@@ -0,0 +1,529 @@
<?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\Portal\Acl;
use Espo\Core\{
Portal\Acl\AccessChecker\ScopeChecker,
Portal\Acl\AccessChecker\ScopeCheckerData,
Portal\Acl\Table,
Acl\ScopeData,
};
class ScopeCheckerTest extends \PHPUnit\Framework\TestCase
{
/**
* @var ScopeChecker
*/
private $scopeChecker;
protected function setUp() : void
{
$this->scopeChecker = new ScopeChecker();
}
public function testCheckerNoData1()
{
$data = ScopeData::fromRaw(false);
$result = $this->scopeChecker->check($data);
$this->assertEquals(false, $result);
}
public function testCheckerNoData2()
{
$data = ScopeData::fromRaw(true);
$result = $this->scopeChecker->check($data);
$this->assertEquals(true, $result);
}
public function testCheckerNoData3()
{
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_NO,
],
);
$result = $this->scopeChecker->check($data);
$this->assertEquals(true, $result);
}
public function testCheckerNoData4()
{
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ALL,
],
);
$result = $this->scopeChecker->check($data);
$this->assertEquals(true, $result);
}
public function testCheckerNoData5()
{
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
],
);
$result = $this->scopeChecker->check($data);
$this->assertEquals(true, $result);
}
public function testCheckerActionNoData1()
{
$data = ScopeData::fromRaw(false);
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE);
$this->assertEquals(false, $result);
}
public function testCheckerActionNoData2()
{
$data = ScopeData::fromRaw(true);
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE);
$this->assertEquals(true, $result);
}
public function testCheckerData1()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ALL,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData2a()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData2b()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_CONTACT,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData3()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_OWN,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData4()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_NO,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData5a()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setInAccount(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_OWN,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData5b()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setInContact(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_OWN,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData6a()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setInAccount(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData6b()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setInContact(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData7a()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData7b()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setInContact(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData7c()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setInContact(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_CONTACT,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData8()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_CREATE => Table::LEVEL_YES,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData9()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_OWN,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData10()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInContact(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_OWN,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData11()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_CONTACT,
],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerData12()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInAccount(true)
->setInContact(true)
->build();
$data = ScopeData::fromRaw(
(object) [],
);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerData13()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$data = ScopeData::fromRaw(false);
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerDataNoAction1()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInAccount(true)
->setInContact(true)
->build();
$data = ScopeData::fromRaw(false);
$result = $this->scopeChecker->check($data, null, $checkerData);
$this->assertEquals(false, $result);
}
public function testCheckerDataNoAction2()
{
$checkerData = ScopeCheckerData
::createBuilder()
->build();
$data = ScopeData::fromRaw(true);
$result = $this->scopeChecker->check($data, null, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerDataNoAction3()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInAccount(true)
->setInContact(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
],
);
$result = $this->scopeChecker->check($data, null, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerDataNoAction4()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInAccount(false)
->setInContact(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_READ => Table::LEVEL_CONTACT,
],
);
$result = $this->scopeChecker->check($data, null, $checkerData);
$this->assertEquals(true, $result);
}
public function testCheckerDataNoAction5()
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInContact(true)
->build();
$data = ScopeData::fromRaw(
(object) [
Table::ACTION_CREATE => Table::LEVEL_NO,
Table::ACTION_READ => Table::LEVEL_NO,
],
);
$result = $this->scopeChecker->check($data, null, $checkerData);
$this->assertEquals(true, $result);
}
}

View File

@@ -0,0 +1,136 @@
<?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\Record;
use Espo\Core\Record\CreateParamsFetcher;
use Espo\Core\Api\RequestWrapper;
use PHPUnit\Framework\TestCase;
class CreateParamsFetcherTest extends TestCase
{
public function test1(): void
{
$request = $this->createMock(RequestWrapper::class);
$request
->method('hasHeader')
->with('X-Skip-Duplicate-Check')
->willReturn(true);
$request
->method('getHeader')
->willReturnMap(
[
['X-Skip-Duplicate-Check', 'true'],
['X-Duplicate-Source-Id', null],
]
);
$params = (new CreateParamsFetcher())->fetch($request);
$this->assertTrue($params->skipDuplicateCheck());
}
public function test2(): void
{
$request = $this->createMock(RequestWrapper::class);
$request
->method('hasHeader')
->willReturn(true);
$request
->method('getHeader')
->willReturnMap(
[
['X-Skip-Duplicate-Check', 'false'],
['X-Duplicate-Source-Id', null],
]
);
$params = (new CreateParamsFetcher())->fetch($request);
$this->assertFalse($params->skipDuplicateCheck());
}
public function test3(): void
{
$request = $this->createMock(RequestWrapper::class);
$request
->method('hasHeader')
->willReturn(false);
$params = (new CreateParamsFetcher())->fetch($request);
$this->assertFalse($params->skipDuplicateCheck());
}
public function test4(): void
{
$request = $this->createMock(RequestWrapper::class);
$request
->method('hasHeader')
->willReturn(true);
$request
->method('getHeader')
->willReturnMap(
[
['X-Skip-Duplicate-Check', 'TRUE'],
['X-Duplicate-Source-Id', null],
]
);
$params = (new CreateParamsFetcher())->fetch($request);
$this->assertTrue($params->skipDuplicateCheck());
}
public function test5(): void
{
$request = $this->createMock(RequestWrapper::class);
$request
->method('hasHeader')
->willReturn(false);
$request
->method('getParsedBody')
->willReturn((object) [
'_skipDuplicateCheck' => true,
]);
$params = (new CreateParamsFetcher())->fetch($request);
$this->assertTrue($params->skipDuplicateCheck());
}
}

View File

@@ -0,0 +1,230 @@
<?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\Record;
use Espo\Core\{
Acl,
Binding\BindingContainer,
Binding\BindingContainerBuilder,
Record\HookManager,
Record\Hook\Provider,
Record\Hook\Type,
Record\CreateParams,
Record\ReadParams,
Record\UpdateParams,
Record\DeleteParams,
InjectableFactory,
Utils\Metadata};
use Espo\Entities\User;
use Espo\ORM\Entity;
use tests\unit\testClasses\Core\Record\Hooks\{
BeforeReadHook,
BeforeCreateHook,
BeforeUpdateHook,
BeforeDeleteHook,
BeforeLinkHook,
BeforeUnlinkHook,
};
class HookManagerTest extends \PHPUnit\Framework\TestCase
{
/**
* @var InjectableFactory
*/
private $injectableFactory;
/**
* @var Metadata
*/
private $metadata;
/**
* @var HookManager
*/
private $manager;
/**
* @var Entity
*/
private $entity;
private $entityType = 'Test';
private ?BindingContainer $bindingContainer;
protected function setUp(): void
{
$this->injectableFactory = $this->createMock(InjectableFactory::class);
$this->metadata = $this->createMock(Metadata::class);
$acl = $this->createMock(Acl::class);
$user = $this->createMock(User::class);
$this->bindingContainer = BindingContainerBuilder::create()
->bindInstance(User::class, $user)
->bindInstance(Acl::class, $acl)
->build();
$provider = new Provider($this->metadata, $this->injectableFactory, $acl, $user);
$this->manager = new HookManager($provider);
$this->entity = $this->createEntity($this->entityType);
}
public function testBeforeRead(): void
{
$hook = $this->createMock(BeforeReadHook::class);
$this->initHooks(Type::BEFORE_READ, [BeforeReadHook::class], [$hook]);
$params = ReadParams::create();
$hook
->expects($this->once())
->method('process')
->with($this->entity, $params);
$this->manager->processBeforeRead($this->entity, $params);
}
public function testBeforeCreate(): void
{
$hook = $this->createMock(BeforeCreateHook::class);
$this->initHooks(Type::BEFORE_CREATE, [BeforeCreateHook::class], [$hook]);
$params = CreateParams::create();
$hook
->expects($this->once())
->method('process')
->with($this->entity, $params);
$this->manager->processBeforeCreate($this->entity, $params);
}
public function testBeforeUpdate(): void
{
$hook = $this->createMock(BeforeUpdateHook::class);
$this->initHooks(Type::BEFORE_UPDATE, [BeforeUpdateHook::class], [$hook]);
$params = UpdateParams::create();
$hook
->expects($this->once())
->method('process')
->with($this->entity, $params);
$this->manager->processBeforeUpdate($this->entity, $params);
}
public function testBeforeDelete(): void
{
$hook = $this->createMock(BeforeDeleteHook::class);
$this->initHooks(Type::BEFORE_DELETE, [BeforeDeleteHook::class], [$hook]);
$params = DeleteParams::create();
$hook
->expects($this->once())
->method('process')
->with($this->entity, $params);
$this->manager->processBeforeDelete($this->entity, $params);
}
public function testBeforeLink(): void
{
$hook = $this->createMock(BeforeLinkHook::class);
$this->initHooks(Type::BEFORE_LINK, [BeforeLinkHook::class], [$hook]);
$link = 'test';
$hook
->expects($this->once())
->method('process')
->with($this->entity, $link, $this->entity);
$this->manager->processBeforeLink($this->entity, $link, $this->entity);
}
public function testBeforeUnlink(): void
{
$hook = $this->createMock(BeforeUnlinkHook::class);
$this->initHooks(Type::BEFORE_UNLINK, [BeforeUnlinkHook::class], [$hook]);
$link = 'test';
$hook
->expects($this->once())
->method('process')
->with($this->entity, $link, $this->entity);
$this->manager->processBeforeUnlink($this->entity, $link, $this->entity);
}
private function createEntity(string $entityType): Entity
{
$entity = $this->createMock(Entity::class);
$entity
->expects($this->any())
->method('getEntityType')
->willReturn($entityType);
return $entity;
}
private function initHooks(string $type, array $hookClassNameList, array $hookList): void
{
$this->metadata
->expects($this->any())
->method('get')
->willReturnMap([
["app.record.{$type}HookClassNameList", [], []],
["recordDefs.$this->entityType.{$type}HookClassNameList", [], $hookClassNameList],
]);
foreach ($hookClassNameList as $i => $className) {
$this->injectableFactory
->expects($this->any())
->method('createWithBinding')
->with($className, $this->bindingContainer)
->willReturn($hookList[$i]);
}
}
}

View File

@@ -0,0 +1,88 @@
<?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\Record;
use Espo\Core\Record\SearchParamsFetcher;
use Espo\Core\Api\RequestWrapper;
use Espo\Core\Utils\Config;
use Espo\Core\Select\Text\MetadataProvider as TextMetadataProvider;
use PHPUnit\Framework\TestCase;
use Slim\Psr7\Factory\RequestFactory;
class SearchParamsFetcherTest extends TestCase
{
private $config;
private $textMetadataProvider;
protected function setUp(): void
{
$this->config = $this->createMocK(Config::class);
$this->textMetadataProvider = $this->createMocK(TextMetadataProvider::class);
$this->config
->method('get')
->with('recordListMaxSizeLimit')
->willReturn(null);
}
public function testFetchJson1(): void
{
$raw = [
'textFilter' => 'test*',
'maxSize' => 10,
];
$q = http_build_query(['searchParams' => json_encode($raw)]);
$request = (new RequestFactory)->createRequest('GET', 'http://localhost/?' . $q);
$fetcher = new SearchParamsFetcher($this->config, $this->textMetadataProvider);
$params = $fetcher->fetch(new RequestWrapper($request));
$this->assertEquals($raw['textFilter'], $params->getTextFilter());
$this->assertEquals($raw['maxSize'], $params->getMaxSize());
}
public function testFetchQuery(): void
{
$q = http_build_query(['attributeSelect' => 'a,b']);
$request = (new RequestFactory)->createRequest('GET', 'http://localhost/?' . $q);
$fetcher = new SearchParamsFetcher($this->config, $this->textMetadataProvider);
$params = $fetcher->fetch(new RequestWrapper($request));
$this->assertEquals(['a', 'b'], $params->getSelect());
}
}

View File

@@ -0,0 +1,144 @@
<?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\Select\AccessControl;
use Espo\Core\Acl;
use Espo\Core\AclManager;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Select\AccessControl\FilterFactory;
use Espo\Core\Select\AccessControl\Filters\OnlyOwn;
use Espo\Core\Select\Helpers\FieldHelper;
use Espo\Core\Utils\Metadata;
use Espo\Entities\User;
use PHPUnit\Framework\TestCase;
class FilterFactoryTest extends TestCase
{
private $aclManager;
private $acl;
private $metadata;
private $injectableFactory;
private $factory;
private $user;
protected function setUp() : void
{
$this->injectableFactory = $this->createMock(InjectableFactory::class);
$this->metadata = $this->createMock(Metadata::class);
$this->user = $this->createMock(User::class);
$this->aclManager = $this->createMock(AclManager::class);
$this->acl = $this->createMock(Acl::class);
$this->factory = new FilterFactory(
$this->injectableFactory,
$this->metadata,
$this->aclManager,
$this->acl,
);
}
public function testCreate1()
{
$this->prepareFactoryTest(null, OnlyOwn::class, 'onlyOwn');
}
public function testCreate2()
{
$this->prepareFactoryTest('SomeClass', OnlyOwn::class, 'onlyOwn');
}
protected function prepareFactoryTest(?string $className, string $defaultClassName, string $name)
{
$entityType = 'Test';
$this->metadata
->expects($this->any())
->method('get')
->willReturnMap([
[['selectDefs', $entityType, 'accessControlFilterClassNameMap', $name], null, $className],
]);
$className = $className ?? $defaultClassName;
$object = $this->createMock($defaultClassName);
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindInstance(User::class, $this->user)
->bindInstance(AclManager::class, $this->aclManager)
->bindInstance(Acl::class, $this->acl);
$binder
->for($className)
->bindValue('$entityType', $entityType);
$binder
->for(FieldHelper::class)
->bindValue('$entityType', $entityType);
$bindingContainer = new BindingContainer($bindingData);
$this->injectableFactory
->expects($this->exactly(1))
->method('createWithBinding')
->with($className, $bindingContainer)
->willReturn($object);
$resultObject = $this->factory->create(
$entityType,
$this->user,
$name
);
$this->assertEquals($object, $resultObject);
$this->assertTrue(
$this->factory->has($entityType, $name)
);
$this->metadata
->expects($this->any())
->method('get')
->willReturnMap([
[['selectDefs', $entityType, 'accessControlFilterClassNameMap', 'badName'], null, null],
]);
$this->assertFalse(
$this->factory->has($entityType, 'badName')
);
}
}

View File

@@ -0,0 +1,150 @@
<?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\Select\AccessControl;
use Espo\Core\Utils\Acl\UserAclManagerProvider;
use Espo\Core\Acl;
use Espo\Core\AclManager;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Portal\Acl as PortalAcl;
use Espo\Core\Portal\AclManager as PortalAclManager;
use Espo\Core\Select\AccessControl\DefaultFilterResolver;
use Espo\Core\Select\AccessControl\DefaultPortalFilterResolver;
use Espo\Core\Select\AccessControl\FilterResolverFactory;
use Espo\Core\Utils\Metadata;
use Espo\Entities\User;
use PHPUnit\Framework\TestCase;
class FilterResolverFactoryTest extends TestCase
{
private $injectableFactory;
private $metadata;
private $user;
private $aclManager;
private $acl;
private $factory;
protected function setUp() : void
{
$this->injectableFactory = $this->createMock(InjectableFactory::class);
$this->metadata = $this->createMock(Metadata::class);
$this->user = $this->createMock(User::class);
$this->aclManager = $this->createMock(AclManager::class);
$this->acl = $this->createMock(Acl::class);
$this->factory = new FilterResolverFactory(
$this->injectableFactory,
$this->metadata,
$this->aclManager,
$this->acl,
);
}
public function testCreate1()
{
$this->prepareFactoryTest(null);
}
public function testCreate2()
{
$this->prepareFactoryTest('SomeClass');
}
public function testCreatePortal()
{
$this->user
->expects($this->any())
->method('isPortal')
->willReturn(true);
$this->prepareFactoryTest(null);
}
protected function prepareFactoryTest(?string $className)
{
$entityType = 'Test';
if (!$this->user->isPortal()) {
$defaultClassName = DefaultFilterResolver::class;
} else {
$defaultClassName = DefaultPortalFilterResolver::class;
}
$this->metadata
->expects($this->once())
->method('get')
->with([
'selectDefs', $entityType,
!$this->user->isPortal() ?
'accessControlFilterResolverClassName':
'portalAccessControlFilterResolverClassName'
])
->willReturn($className);
$className = $className ?? $defaultClassName;
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindInstance(User::class, $this->user)
->bindInstance(AclManager::class, $this->aclManager)
->bindInstance(Acl::class, $this->acl);
if ($this->user->isPortal()) {
$binder->bindInstance(PortalAcl::class, $this->acl);
$binder->bindInstance(PortalAclManager::class, $this->aclManager);
}
$binder
->for($className)
->bindValue('$entityType', $entityType);
$bindingContainer = new BindingContainer($bindingData);
$object = $this->createMock($defaultClassName);
$this->injectableFactory
->expects($this->once())
->method('createWithBinding')
->with($className, $bindingContainer)
->willReturn($object);
$resultObject = $this->factory->create($entityType, $this->user);
$this->assertEquals($object, $resultObject);
}
}

View File

@@ -0,0 +1,158 @@
<?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\Select\AccessControl;
use Espo\Core\Acl;
use Espo\Core\Portal\Acl as AclPortal;
use Espo\Core\Select\AccessControl\DefaultFilterResolver;
use Espo\Core\Select\AccessControl\DefaultPortalFilterResolver;
use PHPUnit\Framework\TestCase;
class FilterResolverTest extends TestCase
{
private $acl;
private $aclPortal;
private $entityType;
private $resolver;
protected function setUp(): void
{
$this->acl = $this->createMock(Acl::class);
$this->aclPortal = $this->createMock(AclPortal::class);
$this->entityType = 'Test';
}
public function testResolveRegularOnlyOwn()
{
$this->assertEquals(
'onlyOwn',
$this->initResolveTest(false, 'checkReadOnlyOwn')
);
}
public function testResolveRegularOnlyTeam()
{
$this->assertEquals(
'onlyTeam',
$this->initResolveTest(false, 'checkReadOnlyTeam')
);
}
public function testResolveRegularNo()
{
$this->assertEquals(
'no',
$this->initResolveTest(false, 'checkReadNo')
);
}
public function testResolvePortalOnlyOwn()
{
$this->assertEquals(
'portalOnlyOwn',
$this->initResolveTest(true, 'checkReadOnlyOwn')
);
}
public function testResolvePortalOnlyAccount()
{
$this->assertEquals(
'portalOnlyAccount',
$this->initResolveTest(true, 'checkReadOnlyAccount')
);
}
public function testResolvePortalOnlyContact()
{
$this->assertEquals(
'portalOnlyContact',
$this->initResolveTest(true, 'checkReadOnlyContact')
);
}
public function testResolvePortalNo()
{
$this->assertEquals(
'no',
$this->initResolveTest(true, 'checkReadNo')
);
}
public function testResolveAll()
{
$this->assertEquals(
'all',
$this->initResolveTest(false, 'checkReadAll')
);
}
public function testResolvePortalAll()
{
$this->assertEquals(
'portalAll',
$this->initResolveTest(true, 'checkReadAll')
);
}
protected function initResolveTest(bool $isPortal = false, ?string $method = null): ?string
{
$acl = $this->acl;
if ($isPortal) {
$acl = $this->aclPortal;
}
if (!$isPortal) {
$this->resolver = new DefaultFilterResolver(
$this->entityType,
$acl
);
}
if ($isPortal) {
$this->resolver = new DefaultPortalFilterResolver(
$this->entityType,
$acl
);
}
if ($method) {
$acl
->expects($this->any())
->method($method)
->with($this->entityType)
->willReturn(true);
}
return $this->resolver->resolve();
}
}

View File

@@ -0,0 +1,348 @@
<?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\Select\AccessControl;
use Espo\Core\Field\LinkMultiple;
use Espo\Core\Field\LinkMultipleItem;
use Espo\Core\Portal\Acl\OwnershipChecker\MetadataProvider;
use Espo\Core\Select\AccessControl\Filter as AccessControlFilter;
use Espo\Core\Select\AccessControl\Filters\No;
use Espo\Core\Select\AccessControl\Filters\PortalOnlyAccount;
use Espo\Core\Select\AccessControl\Filters\PortalOnlyContact;
use Espo\Core\Select\AccessControl\Filters\PortalOnlyOwn;
use Espo\Core\Select\Helpers\FieldHelper;
use Espo\Core\Select\Helpers\RelationQueryHelper;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\ORM\Defs;
use Espo\ORM\Defs\RelationDefs;
use Espo\ORM\Query\Part\Where\OrGroup;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
use Espo\ORM\Type\RelationType;
use PHPUnit\Framework\TestCase;
class FiltersTest extends TestCase
{
private $queryBuilder;
private $fieldHelper;
private $user;
private $entityType;
protected function setUp(): void
{
$this->queryBuilder = $this->createMock(QueryBuilder::class);
$this->fieldHelper = $this->createMock(FieldHelper::class);
$this->user = $this->createMock(User::class);
$this->user->set('id', 'user-id');
$this->user
->expects($this->any())
->method('getId')
->willReturn('user-id');
$this->entityType = 'Test';
$this->user
->expects($this->any())
->method('getTeamIdList')
->willReturn(['team-id']);
}
public function testNo(): void
{
$filter = $this->createFilter(No::class);
$this->queryBuilder
->expects($this->once())
->method('where')
->with([
'id' => null,
]);
$filter->apply($this->queryBuilder);
}
public function testPortalOnlyAccount1(): void
{
$defs = $this->createMock(Defs::class);
$metadataProvider = $this->createMock(MetadataProvider::class);
$queryHelper = new RelationQueryHelper($defs);
$filter = new PortalOnlyAccount(
'Test',
$this->user,
$this->fieldHelper,
$metadataProvider,
$queryHelper
);
$metadataProvider
->expects($this->any())
->method('getContactLink')
->willReturn(
RelationDefs::fromRaw([
'type' => RelationType::BELONGS_TO_PARENT,
'entityList' => [Contact::ENTITY_TYPE],
], 'contact')
);
$metadataProvider
->expects($this->any())
->method('getAccountLink')
->willReturn(
RelationDefs::fromRaw([
'type' => RelationType::MANY_MANY,
'entity' => [Account::ENTITY_TYPE],
'midKeys' => ['nId', 'fId'],
'relationName' => 'TestAccount'
], 'account')
);
$this->user
->expects($this->any())
->method('getAccounts')
->willReturn(LinkMultiple::create([
LinkMultipleItem::create('account-id')
]));
$this->user
->expects($this->any())
->method('getContactId')
->willReturn('contact-id');
$this->initHelperMethods([
['hasCreatedByField', true],
]);
$this->queryBuilder
->expects($this->once())
->method('where')
->with(
$this->callback(function ($where) {
if (!$where instanceof OrGroup) {
return false;
}
if (!isset($where->getRawValue()[0]['id=s'])) {
return false;
}
return $where->getItemCount() === 3;
})
)
->willReturn($this->queryBuilder);
$filter->apply($this->queryBuilder);
}
public function testPortalOnlyAccount2(): void
{
$defs = $this->createMock(Defs::class);
$metadataProvider = $this->createMock(MetadataProvider::class);
$queryHelper = new RelationQueryHelper($defs);
$filter = new PortalOnlyAccount(
'Test',
$this->user,
$this->fieldHelper,
$metadataProvider,
$queryHelper
);
$this->user
->expects($this->any())
->method('getLinkMultipleIdList')
->with('accounts')
->willReturn([]);
$this->user
->expects($this->any())
->method('get')
->with('contactId')
->willReturn(null);
$this->initHelperMethods([
['hasCreatedByField', false],
]);
$this->queryBuilder
->expects($this->once())
->method('where')
->with([
'id' => null,
]);
$filter->apply($this->queryBuilder);
}
public function testPortalOnlyContact1(): void
{
$defs = $this->createMock(Defs::class);
$metadataProvider = $this->createMock(MetadataProvider::class);
$queryHelper = new RelationQueryHelper($defs);
$filter = new PortalOnlyContact(
'Test',
$this->user,
$this->fieldHelper,
$metadataProvider,
$queryHelper
);
$this->user
->expects($this->any())
->method('getContactId')
->willReturn('contact-id');
$this->initHelperMethods([
['hasCreatedByField', true],
]);
$metadataProvider
->expects($this->once())
->method('getContactLink')
->willReturn(
RelationDefs::fromRaw([
'type' => RelationType::BELONGS_TO,
'entity' => Contact::ENTITY_TYPE,
], 'contact')
);
$this->queryBuilder
->expects($this->once())
->method('where')
->with(
$this->callback(function ($where) {
return $where instanceof OrGroup &&
$where->getItemCount() === 2;
})
)
->willReturn($this->queryBuilder);
$filter->apply($this->queryBuilder);
}
public function testPortalOnlyContact2()
{
$defs = $this->createMock(Defs::class);
$metadataProvider = $this->createMock(MetadataProvider::class);
$queryHelper = new RelationQueryHelper($defs);
$filter = new PortalOnlyContact(
'Test',
$this->user,
$this->fieldHelper,
$metadataProvider,
$queryHelper
);
$this->user
->expects($this->any())
->method('get')
->with('contactId')
->willReturn(null);
$this->initHelperMethods([
['hasCreatedByField', false],
]);
$this->queryBuilder
->expects($this->once())
->method('where')
->with([
'id' => null,
])
->willReturn($this->queryBuilder);
$filter->apply($this->queryBuilder);
}
public function testPortalOnlyOwn1()
{
$filter = $this->createFilter(PortalOnlyOwn::class);
$this->initHelperMethods([
['hasCreatedByField', true],
]);
$this->queryBuilder
->expects($this->once())
->method('where')
->with([
'createdById' => $this->user->getId(),
])
->willReturn($this->queryBuilder);
$filter->apply($this->queryBuilder);
}
public function testPortalOnlyOwn2()
{
$filter = $this->createFilter(PortalOnlyOwn::class);
$this->initHelperMethods([
['hasCreatedByField', false],
]);
$this->queryBuilder
->expects($this->once())
->method('where')
->with([
'id' => null,
])
->willReturn($this->queryBuilder);
$filter->apply($this->queryBuilder);
}
protected function initHelperMethods(array $map)
{
foreach ($map as $i => $item) {
$this->fieldHelper
->expects($this->once())
->method($item[0])
->willReturn($item[1]);
}
}
protected function createFilter(string $className): AccessControlFilter
{
return new $className(
$this->user,
$this->fieldHelper
);
}
}

View File

@@ -0,0 +1,147 @@
<?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\Select\Applier\Appliers;
use Espo\Core\Select\AccessControl\Applier as AccessControlFilterApplier;
use Espo\Core\Select\AccessControl\Filter as AccessControlFilter;
use Espo\Core\Select\AccessControl\FilterFactory as AccessControlFilterFactory;
use Espo\Core\Select\AccessControl\FilterResolver;
use Espo\Core\Select\AccessControl\FilterResolverFactory as AccessControlFilterResolverFactory;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
use PHPUnit\Framework\TestCase;
class AccessControlFilterApplierTest extends TestCase
{
private $filterFactory;
private $filterResolverFactory;
private $user;
private $queryBuilder;
private $filterResolver;
private $filter;
private $mandatoryFilter;
private $entityType;
private $applier;
protected function setUp() : void
{
$this->filterFactory = $this->createMock(AccessControlFilterFactory::class);
$this->filterResolverFactory = $this->createMock(AccessControlFilterResolverFactory::class);
$this->user = $this->createMock(User::class);
$this->queryBuilder = $this->createMock(QueryBuilder::class);
$this->filterResolver = $this->createMock(FilterResolver::class);
$this->filter = $this->createMock(AccessControlFilter::class);
$this->mandatoryFilter = $this->createMock(AccessControlFilter::class);
$this->entityType = 'Test';
$this->applier = new AccessControlFilterApplier(
entityType: $this->entityType,
user: $this->user,
accessControlFilterFactory: $this->filterFactory,
accessControlFilterResolverFactory: $this->filterResolverFactory,
);
}
public function testApply1()
{
$this->initApplierTest(true, true);
$this->applier->apply($this->queryBuilder);
}
public function testApply2()
{
$this->initApplierTest(true, false);
$this->applier->apply($this->queryBuilder);
}
public function testApply3()
{
$this->initApplierTest(false, false);
$this->applier->apply($this->queryBuilder);
}
protected function initApplierTest(bool $resolve, bool $hasFilter)
{
$this->filterResolverFactory
->expects($this->once())
->method('create')
->with($this->entityType, $this->user)
->willReturn($this->filterResolver);
$filterName = null;
if ($resolve) {
$filterName = 'test';
}
$this->filterResolver
->expects($this->once())
->method('resolve')
->willReturn($filterName);
if (!$resolve) {
$this->filterFactory
->expects($this->never())
->method('has');
return;
}
$this->filterFactory
->expects($this->once())
->method('has')
->with($this->entityType, $filterName)
->willReturn($hasFilter);
if (!$hasFilter) {
$this->expectException(\RuntimeException::class);
return;
}
$this->filterFactory
->expects($this->exactly(2))
->method('create')
->willReturnMap([
[$this->entityType, $this->user, 'mandatory', $this->mandatoryFilter],
[$this->entityType, $this->user, $filterName, $this->filter],
]);
$this->filter
->expects($this->once())
->method('apply')
->with($this->queryBuilder);
}
}

View File

@@ -0,0 +1,152 @@
<?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\Select\Applier\Appliers;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Select\Bool\Applier as BoolFilterListApplier;
use Espo\Core\Select\Bool\Filter as BoolFilter;
use Espo\Core\Select\Bool\FilterFactory as BoolFilterFactory;
use Espo\Entities\User;
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
use PHPUnit\Framework\TestCase;
class BoolFilterListApplierTest extends TestCase
{
private $boolFilterFactory;
private $user;
private $queryBuilder;
private $entityType;
private $applier;
protected function setUp(): void
{
$this->boolFilterFactory = $this->createMock(BoolFilterFactory::class);
$this->user = $this->createMock(User::class);
$this->queryBuilder = $this->createMock(QueryBuilder::class);
$this->entityType = 'Test';
$this->applier = new BoolFilterListApplier(
entityType: $this->entityType,
user: $this->user,
boolFilterFactory: $this->boolFilterFactory,
);
}
public function testApply1()
{
$boolFilterList = ['test1', 'test2'];
$filter1 = $this->createFilterMock(['test' => '1']);
$filter2 = $this->createFilterMock(['test' => '2']);
$this->initApplierTest($boolFilterList, [$filter1, $filter2], [true, true]);
$this->queryBuilder
->expects($this->once())
->method('where');
$this->applier->apply($this->queryBuilder, $boolFilterList);
}
public function testApply2()
{
$boolFilterList = ['test1'];
$filter1 = $this->createFilterMock(['test' => '1']);
$this->initApplierTest($boolFilterList, [$filter1], [true]);
$this->queryBuilder
->expects($this->once())
->method('where');
$this->applier->apply($this->queryBuilder, $boolFilterList);
}
public function testApply3()
{
$boolFilterList = ['test1'];
$this->initApplierTest($boolFilterList, [null], [false]);
$this->expectException(BadRequest::class);
$this->applier->apply($this->queryBuilder, $boolFilterList);
}
protected function initApplierTest(array $filterNameList, array $filterList, array $hasList)
{
$hasMap = [];
$createMap = [];
foreach ($filterNameList as $i => $filterName) {
$hasMap[] = [$this->entityType, $filterName, $hasList[$i]];
if (!$hasList[$i]) {
continue;
}
$createMap[] = [$this->entityType, $this->user, $filterName, $filterList[$i]];
}
$this->boolFilterFactory
->expects($this->any())
->method('has')
->willReturnMap($hasMap);
$this->boolFilterFactory
->expects($this->any())
->method('create')
->willReturnMap($createMap);
}
protected function createFilterMock(array $rawWhereClause): BoolFilter
{
$filter = $this->createMock(BoolFilter::class);
$whereClause = $this->createMock(WhereClause::class);
$whereClause
->expects($this->any())
->method('getRawValue')
->willReturn($rawWhereClause);
$filter
->expects($this->any())
->method('apply')
->with($this->queryBuilder, $this->isInstanceOf(OrGroupBuilder::class));
return $filter;
}
}

View File

@@ -0,0 +1,339 @@
<?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\Select\Applier\Appliers;
use Espo\Core\Acl\SystemRestriction;
use Espo\Core\AclManager;
use Espo\Core\Exceptions\BadRequest;
use Espo\Entities\User;
use Espo\ORM\Query\Part\OrderList;
use Espo\ORM\Query\Part\Order;
use Espo\Core\Select\Order\Applier as OrderApplier;
use Espo\Core\Select\Order\Item;
use Espo\Core\Select\Order\ItemConverter;
use Espo\Core\Select\Order\ItemConverterFactory;
use Espo\Core\Select\Order\MetadataProvider;
use Espo\Core\Select\Order\Orderer;
use Espo\Core\Select\Order\OrdererFactory;
use Espo\Core\Select\Order\Params as OrderParams;
use Espo\Core\Select\SearchParams;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
use PHPUnit\Framework\TestCase;
class OrderApplierTest extends TestCase
{
private ?OrdererFactory $ordererFactory = null;
private $metadataProvider;
private $itemConverterFactory;
private $queryBuilder;
private $params;
private $entityType;
private $applier;
protected function setUp(): void
{
$this->metadataProvider = $this->createMock(MetadataProvider::class);
$this->itemConverterFactory = $this->createMock(ItemConverterFactory::class);
$this->ordererFactory = $this->createMock(OrdererFactory::class);
$this->queryBuilder = $this->createMock(QueryBuilder::class);
$this->params = $this->createMock(OrderParams::class);
$aclManager = $this->createMock(AclManager::class);
$user = $this->createMock(User::class);
$restriction = $this->createMock(SystemRestriction::class);
$restriction
->method('checkFieldRead')
->willReturn(true);
$this->entityType = 'Test';
$this->applier = new OrderApplier(
entityType: $this->entityType,
metadataProvider: $this->metadataProvider,
itemConverterFactory: $this->itemConverterFactory,
ordererFactory: $this->ordererFactory,
aclManager: $aclManager,
user: $user,
systemRestriction: $restriction,
);
}
public function testApplyDefault()
{
$order = SearchParams::ORDER_DESC;
$this->params
->expects($this->any())
->method('forceDefault')
->willReturn(true);
$this->params
->expects($this->any())
->method('getOrder')
->willReturn($order);
$this->metadataProvider
->expects($this->any())
->method('getDefaultOrderBy')
->with($this->entityType)
->willReturn('testField');
$this->initApplyOrderTest('testField', $order, 'varchar');
$this->applier->apply($this->queryBuilder, $this->params);
}
public function testApply1()
{
$order = SearchParams::ORDER_DESC;
$orderBy = 'testField';
$this->params
->expects($this->any())
->method('forceDefault')
->willReturn(false);
$this->params
->expects($this->any())
->method('getOrder')
->willReturn($order);
$this->params
->expects($this->any())
->method('getOrderBy')
->willReturn($orderBy);
$this->metadataProvider
->expects($this->any())
->method('getDefaultOrderBy')
->with($this->entityType)
->willReturn($orderBy);
$this->initApplyOrderTest($orderBy, $order, 'varchar');
$this->applier->apply($this->queryBuilder, $this->params);
}
public function testApplyWithConverter()
{
$order = SearchParams::ORDER_DESC;
$orderBy = 'testField';
$this->params
->expects($this->any())
->method('forceDefault')
->willReturn(false);
$this->params
->expects($this->any())
->method('getOrder')
->willReturn($order);
$this->params
->expects($this->any())
->method('getOrderBy')
->willReturn($orderBy);
$this->metadataProvider
->expects($this->any())
->method('getDefaultOrderBy')
->with($this->entityType)
->willReturn($orderBy);
$converterResult = OrderList::create([
Order::fromString('hello')->withDesc(),
]);
$this->initApplyOrderTest($orderBy, $order, 'varchar', $converterResult);
$this->applier->apply($this->queryBuilder, $this->params);
}
public function testApplyNotExisting()
{
$order = SearchParams::ORDER_DESC;
$orderBy = 'testField';
$this->params
->expects($this->any())
->method('forceDefault')
->willReturn(false);
$this->params
->expects($this->any())
->method('getOrder')
->willReturn($order);
$this->params
->expects($this->any())
->method('getOrderBy')
->willReturn($orderBy);
$this->metadataProvider
->expects($this->any())
->method('getDefaultOrderBy')
->with($this->entityType)
->willReturn($orderBy);
$this->initApplyOrderTest($orderBy, $order, 'varchar', null, true);
$this->applier->apply($this->queryBuilder, $this->params);
}
public function testApplyWithOrderer()
{
$order = SearchParams::ORDER_DESC;
$orderBy = 'testField';
$this->params
->expects($this->any())
->method('forceDefault')
->willReturn(false);
$this->params
->expects($this->any())
->method('getOrder')
->willReturn($order);
$this->params
->expects($this->any())
->method('getOrderBy')
->willReturn($orderBy);
$this->ordererFactory
->expects($this->once())
->method('has')
->with($this->entityType, $orderBy)
->willReturn(true);
$orderer = $this->createMock(Orderer::class);
$this->ordererFactory
->expects($this->once())
->method('create')
->with($this->entityType, $orderBy)
->willReturn($orderer);
$orderer
->expects($this->once())
->method('apply')
->with($this->queryBuilder, Item::create($orderBy, $order));
$this->applier->apply($this->queryBuilder, $this->params);
}
protected function initApplyOrderTest(
string $orderBy,
string $order,
string $fieldType,
?OrderList $converterResult = null,
bool $notExisting = false
) {
$this->metadataProvider
->expects($this->any())
->method('getFieldType')
->with($this->entityType, $orderBy)
->willReturn($fieldType);
$this->itemConverterFactory
->expects($this->once())
->method('has')
->with($this->entityType, $orderBy)
->willReturn(
(bool) $converterResult
);
$this->metadataProvider
->expects($this->any())
->method('hasAttribute')
->willReturnMap(
[
[$this->entityType, $orderBy, !$notExisting],
[$this->entityType, 'id', true],
]
);
if ($converterResult) {
$converter = $this->createMock(ItemConverter::class);
$this->itemConverterFactory
->expects($this->once())
->method('create')
->with($this->entityType, $orderBy)
->willReturn($converter);
$item = Item::create($orderBy, $order);
$converter
->expects($this->once())
->method('convert')
->with($item)
->willReturn($converterResult);
} else {
if ($notExisting) {
$this->expectException(BadRequest::class);
return;
}
$this->metadataProvider
->expects($this->once())
->method('isAttributeParamUniqueTrue')
->with($this->entityType, $orderBy)
->willReturn(false);
}
$expectedOrderBy = ($converterResult ? $this->orderListToArray($converterResult): null)
?? [[$orderBy, $order]];
$expectedOrderBy[] = ['id', $order];
$this->queryBuilder
->expects($this->once())
->method('order')
->with($expectedOrderBy);
}
private function orderListToArray(OrderList $orderList): array
{
$list = [];
foreach ($orderList as $order) {
$list[] = [
$order->getExpression()->getValue(),
$order->getDirection(),
];
}
return $list;
}
}

View File

@@ -0,0 +1,108 @@
<?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\Select\Applier\Appliers;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Select\Primary\Applier as PrimaryFilterApplier;
use Espo\Core\Select\Primary\Filter as PrimaryFilter;
use Espo\Core\Select\Primary\FilterFactory as PrimaryFilterFactory;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
use PHPUnit\Framework\TestCase;
class PrimaryFilterApplierTest extends TestCase
{
private $filterFactory;
private $user;
private $queryBuilder;
private $entityType;
private $applier;
protected function setUp(): void
{
$this->filterFactory = $this->createMock(PrimaryFilterFactory::class);
$this->user = $this->createMock(User::class);
$this->queryBuilder = $this->createMock(QueryBuilder::class);
$this->entityType = 'Test';
$this->applier = new PrimaryFilterApplier(
entityType: $this->entityType,
user: $this->user,
primaryFilterFactory: $this->filterFactory,
);
}
public function testApply1()
{
$filterName = 'test';
$filter = $this->createMock(PrimaryFilter::class);
$filter
->expects($this->once())
->method('apply')
->with($this->queryBuilder);
$this->filterFactory
->expects($this->once())
->method('has')
->with($this->entityType, $filterName)
->willReturn(true);
$this->filterFactory
->expects($this->once())
->method('create')
->with($this->entityType, $this->user, $filterName)
->willReturn($filter);
$this->applier->apply($this->queryBuilder, $filterName);
}
public function testApply2()
{
$filterName = 'test';
$this->filterFactory
->expects($this->once())
->method('has')
->with($this->entityType, $filterName)
->willReturn(false);
$this->filterFactory
->expects($this->never())
->method('create');
$this->expectException(BadRequest::class);
$this->applier->apply($this->queryBuilder, $filterName);
}
}

View File

@@ -0,0 +1,196 @@
<?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\Select\Applier\Appliers;
use Espo\Core\Select\SearchParams;
use Espo\Core\Select\Select\Applier as SelectApplier;
use Espo\Core\Select\Select\MetadataProvider;
use Espo\Core\Utils\FieldUtil;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
use PHPUnit\Framework\TestCase;
class SelectApplierTest extends TestCase
{
private $metadataProvider;
private $queryBuilder;
private $applier;
private $entityType;
private $fieldUtil;
private $searchParams;
private $user;
protected function setUp(): void
{
$this->user = $this->createMock(User::class);
$this->metadataProvider = $this->createMock(MetadataProvider::class);
$this->queryBuilder = $this->createMock(QueryBuilder::class);
$this->searchParams = $this->createMock(SearchParams::class);
$this->fieldUtil = $this->createMock(FieldUtil::class);
$this->entityType = 'Test';
$this->applier = new SelectApplier(
$this->entityType,
$this->user,
$this->fieldUtil,
$this->metadataProvider
);
}
public function testApply1()
{
$this->initTestApply();
}
public function testApplyPortal()
{
$this->initTestApply(true);
}
protected function initTestApply(bool $isPortal = false)
{
$select = ['testSelect', 'testNotExisting', 'testText'];
$orderBy = 'testOrder';
$aclAttributeList = ['testAcl'];
$aclPortalAttributeList = ['testAclPortal'];
$dependencyMap = [
'testSelect' => [
'testDependency',
],
];
$aclAttribute = $aclAttributeList[0];
if ($isPortal) {
$aclAttribute = $aclPortalAttributeList[0];
}
$this->user
->expects($this->any())
->method('isPortal')
->willReturn($isPortal);
$this->searchParams
->expects($this->any())
->method('getMaxTextAttributeLength')
->willReturn(100);
$this->searchParams
->expects($this->any())
->method('getSelect')
->willReturn($select);
$this->searchParams
->expects($this->any())
->method('getOrderBy')
->willReturn($orderBy);
$this->metadataProvider
->expects($this->any())
->method('getAclAttributeList')
->willReturn($aclAttributeList);
$this->metadataProvider
->expects($this->any())
->method('getAclPortalAttributeList')
->willReturn($aclPortalAttributeList);
$this->metadataProvider
->expects($this->any())
->method('getSelectAttributesDependencyMap')
->with($this->entityType)
->willReturn($dependencyMap);
$this->fieldUtil
->expects($this->once())
->method('getAttributeList')
->with($this->entityType, $orderBy)
->willReturn([$orderBy]);
$this->metadataProvider
->expects($this->any())
->method('hasAttribute')
->willReturnMap(
[
[$this->entityType, 'id', true],
[$this->entityType, 'testSelect', true],
[$this->entityType, 'testNotExisting', false],
[$this->entityType, 'testOrder', true],
[$this->entityType, 'testAcl', true],
[$this->entityType, 'testAclPortal', true],
[$this->entityType, 'testDependency', true],
[$this->entityType, 'testText', true],
]
);
$this->metadataProvider
->expects($this->any())
->method('isAttributeNotStorable')
->willReturn(false);
$this->metadataProvider
->expects($this->any())
->method('getAttributeType')
->willReturnMap(
[
[$this->entityType, 'id', Entity::ID],
[$this->entityType, 'testSelect', Entity::VARCHAR],
[$this->entityType, 'testOrder', Entity::VARCHAR],
[$this->entityType, 'testAcl', Entity::VARCHAR],
[$this->entityType, 'testAclPortal', Entity::VARCHAR],
[$this->entityType, 'testDependency', Entity::VARCHAR],
[$this->entityType, 'testText', Entity::TEXT],
]
);
$expected = [
'id',
$aclAttribute,
'testSelect',
['LEFT:(testText, 100)', 'testText'],
'testOrder',
'testDependency',
];
$this->queryBuilder
->expects($this->once())
->method('select')
->with($expected);
$this->applier->apply($this->queryBuilder, $this->searchParams);
}
}

View File

@@ -0,0 +1,269 @@
<?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\Select\Applier\Appliers;
use Espo\Core\Select\Text\Applier as TextFilterApplier;
use Espo\Core\Select\Text\ConfigProvider;
use Espo\Core\Select\Text\DefaultFilter;
use Espo\Core\Select\Text\FilterFactory;
use Espo\Core\Select\Text\FilterParams;
use Espo\Core\Select\Text\FullTextSearch\Data as FullTextSearchData;
use Espo\Core\Select\Text\FullTextSearch\DataComposer\Params as FullTextSearchDataComposerParams;
use Espo\Core\Select\Text\FullTextSearch\DataComposerFactory as FullTextSearchDataComposerFactory;
use Espo\Core\Select\Text\FullTextSearch\DefaultDataComposer as FullTextSearchDataComposer;
use Espo\Core\Select\Text\MetadataProvider;
use Espo\Core\Utils\Config;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\ORM\Query\Part\Expression as Expr;
use Espo\ORM\Query\Part\Where\OrGroup;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
use PHPUnit\Framework\TestCase;
class TextFilterApplierTest extends TestCase
{
private $metadataProvider;
private $queryBuilder;
private $filterParams;
private $config;
private $fullTextSearchDataComposerFactory;
private $filterFactory;
private $applier;
private $entityType;
protected function setUp(): void
{
$user = $this->createMock(User::class);
$this->metadataProvider = $this->createMock(MetadataProvider::class);
$this->queryBuilder = $this->createMock(QueryBuilder::class);
$this->filterParams = $this->createMock(FilterParams::class);
$this->config = $this->createMock(Config::class);
$this->fullTextSearchDataComposerFactory = $this->createMock(FullTextSearchDataComposerFactory::class);
$this->filterFactory = $this->createMock(FilterFactory::class);
$configProvider = $this->createMock(ConfigProvider::class);
$this->entityType = 'Test';
$this->applier = new TextFilterApplier(
$this->entityType,
$user,
$this->metadataProvider,
$this->fullTextSearchDataComposerFactory,
$this->filterFactory,
$configProvider
);
}
public function testApply1()
{
$this->initTest(false);
}
public function testApply2()
{
$this->initTest(true);
}
public function testApply3()
{
$this->initTest(false, '1000');
}
protected function initTest(bool $noFullTextSearch = false, ?string $filter = null)
{
$filter = $filter ?? 'test';
$filterParams = $this->createMock(FilterParams::class);
$filterParams
->expects($this->any())
->method('noFullTextSearch')
->willReturn($noFullTextSearch);
$this->config
->expects($this->any())
->method('get')
->willReturnMap(
[
['textFilterUseContainsForVarchar', false],
['textFilterContainsMinLength', 4],
]
);
$configProvider = new ConfigProvider($this->config);
$defaultFilter = new DefaultFilter(
$this->entityType,
$this->metadataProvider,
$configProvider
);
$this->filterFactory
->expects($this->once())
->method('create')
->willReturn(
$defaultFilter
);
$this->metadataProvider
->expects($this->any())
->method('getFullTextSearchOrderType')
->with($this->entityType)
->willReturn(null);
$this->metadataProvider
->expects($this->any())
->method('getAttributeType')
->willReturnMap(
[
[$this->entityType, 'fieldVarchar', Entity::VARCHAR],
[$this->entityType, 'fieldText', Entity::TEXT],
[$this->entityType, 'fieldFullText', Entity::TEXT],
[$this->entityType, 'fieldInt', Entity::INT],
[$this->entityType, 'fieldForeign', Entity::FOREIGN],
['ForeignEntityType', 'field', Entity::VARCHAR],
]
);
$this->metadataProvider
->expects($this->any())
->method('getAttributeRelationParam')
->with($this->entityType, 'fieldForeign')
->willReturn('link1');
$this->metadataProvider
->expects($this->any())
->method('getRelationType')
->with($this->entityType, 'link2')
->willReturn(Entity::HAS_MANY);
$this->metadataProvider
->expects($this->any())
->method('getRelationEntityType')
->with($this->entityType, 'link2')
->willReturn('ForeignEntityType');
$this->metadataProvider
->expects($this->any())
->method('getTextFilterAttributeList')
->with($this->entityType)
->willReturn(
['fieldVarchar', 'fieldText', 'fieldFullText', 'fieldInt', 'fieldForeign', 'link2.field']
);
if (!$noFullTextSearch) {
$this->initFullTextSearchData($filter, ['fieldFullText'], 'TEST:(test)');
}
$expectedWhere = [
['fieldVarchar*' => $filter . '%'],
['fieldText*' => '%' . $filter . '%']
];
if (is_numeric($filter)) {
$expectedWhere[] = ['fieldInt=' => intval($filter)];
}
if ($noFullTextSearch) {
$expectedWhere[] = ['fieldFullText*' => '%' . $filter . '%'];
}
$expectedWhere[] = ['fieldForeign*' => $filter . '%'];
$expectedWhere[] = ['link2.field*' => $filter . '%'];
if (!$noFullTextSearch) {
$expectedWhere[] = ['NOT_EQUAL:(TEST:(test), 0):' => null];
}
$this->queryBuilder
->expects($this->once())
->method('where')
->with(OrGroup::fromRaw($expectedWhere));
$c = $this->exactly(2);
$this->queryBuilder
->expects($c)
->method('leftJoin')
->willReturnCallback(function ($link) use ($c) {
if ($c->numberOfInvocations() === 1) {
$this->assertEquals('link1', $link);
}
if ($c->numberOfInvocations() === 2) {
$this->assertEquals('link2', $link);
}
return $this->queryBuilder;
});
$this->queryBuilder
->expects($this->once())
->method('distinct');
$this->applier->apply($this->queryBuilder, $filter, $this->filterParams);
}
protected function initFullTextSearchData(string $filter, array $fieldList, string $expression)
{
$fullTextSearchData = $this->createMock(FullTextSearchData::class);
$fullTextSearchDataComposer = $this->createMock(FullTextSearchDataComposer::class);
$fullTextSearchDataComposerParams = FullTextSearchDataComposerParams::create();
$this->fullTextSearchDataComposerFactory
->expects($this->once())
->method('create')
->with($this->entityType)
->willReturn($fullTextSearchDataComposer);
$fullTextSearchData
->expects($this->any())
->method('getFieldList')
->willReturn($fieldList);
$fullTextSearchData
->expects($this->any())
->method('getExpression')
->willReturn(Expr::create($expression));
$fullTextSearchDataComposer
->expects($this->any())
->method('compose')
->with($filter, $fullTextSearchDataComposerParams)
->willReturn($fullTextSearchData);
}
}

View File

@@ -0,0 +1,131 @@
<?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\Select\Applier\Appliers;
use Espo\Core\Select\Where\Applier as WhereApplier;
use Espo\Core\Select\Where\Checker;
use Espo\Core\Select\Where\CheckerFactory;
use Espo\Core\Select\Where\Converter;
use Espo\Core\Select\Where\ConverterFactory;
use Espo\Core\Select\Where\Item as WhereItem;
use Espo\Core\Select\Where\Params;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
use PHPUnit\Framework\TestCase;
class WhereApplierTest extends TestCase
{
private $user;
private $queryBuilder;
private $whereItem;
private $converterFactory;
private $converter;
private $checkerFactory;
private $checker;
private $params;
private $entityType;
private $applier;
protected function setUp(): void
{
$this->user = $this->createMock(User::class);
$this->queryBuilder = $this->createMock(QueryBuilder::class);
$this->whereItem = $this->createMock(WhereItem::class);
$this->converterFactory = $this->createMock(ConverterFactory::class);
$this->converter = $this->createMock(Converter::class);
$this->checkerFactory = $this->createMock(CheckerFactory::class);
$this->checker = $this->createMock(Checker::class);
$this->params = $this->createMock(Params::class);
$this->entityType = 'Test';
$this->applier = new WhereApplier(
$this->entityType,
$this->user,
$this->converterFactory,
$this->checkerFactory
);
}
public function testApply1()
{
$this->checker
->expects($this->once())
->method('check')
->with($this->whereItem, $this->params);
$this->checkerFactory
->expects($this->once())
->method('create')
->with($this->entityType, $this->user)
->willReturn($this->checker);
$this->converter
->expects($this->once())
->method('convert')
->with($this->queryBuilder, $this->whereItem);
$this->converterFactory
->expects($this->once())
->method('create')
->with($this->entityType, $this->user)
->willReturn($this->converter);
$this->applier->apply($this->queryBuilder, $this->whereItem, $this->params);
}
public function testApply2()
{
$this->checker
->expects($this->once())
->method('check')
->with($this->whereItem, $this->params);
$this->checkerFactory
->expects($this->once())
->method('create')
->with($this->entityType, $this->user)
->willReturn($this->checker);
$this->converter
->expects($this->once())
->method('convert')
->with($this->queryBuilder, $this->whereItem);
$this->converterFactory
->expects($this->once())
->method('create')
->with($this->entityType, $this->user)
->willReturn($this->converter);
$this->applier->apply($this->queryBuilder, $this->whereItem, $this->params);
}
}

View File

@@ -0,0 +1,171 @@
<?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\Select\Applier;
use Espo\Core\Acl;
use Espo\Core\AclManager;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Select\AccessControl\Applier as AccessControlFilterApplier;
use Espo\Core\Select\Applier\Appliers\Additional as AdditionalApplier;
use Espo\Core\Select\Applier\Appliers\Limit as LimitApplier;
use Espo\Core\Select\Applier\Factory as ApplierFactory;
use Espo\Core\Select\Bool\Applier as BoolFilterListApplier;
use Espo\Core\Select\Order\Applier as OrderApplier;
use Espo\Core\Select\Primary\Applier as PrimaryFilterApplier;
use Espo\Core\Select\Select\Applier as SelectApplier;
use Espo\Core\Select\Text\Applier as TextFilterApplier;
use Espo\Core\Select\Where\Applier as WhereApplier;
use Espo\Core\Utils\Acl\UserAclManagerProvider;
use Espo\Entities\User;
use PHPUnit\Framework\TestCase;
class FactoryTest extends TestCase
{
private $aclManager;
private $acl;
private $injectableFactory;
private $user;
private $factory;
protected function setUp(): void
{
$this->injectableFactory = $this->createMock(InjectableFactory::class);
$this->user = $this->createMock(User::class);
$userAclManagerProvider = $this->createMock(UserAclManagerProvider::class);
$this->aclManager = $this->createMock(AclManager::class);
$userAclManagerProvider
->expects($this->any())
->method('get')
->willReturn($this->aclManager);
$this->factory = new ApplierFactory(
$this->injectableFactory,
$userAclManagerProvider,
);
$this->acl = $this->createMock(Acl::class);
$this->aclManager
->expects($this->any())
->method('createUserAcl')
->willReturn($this->acl);
}
public function testCreate1()
{
$this->prepareFactoryTest(SelectApplier::class, ApplierFactory::SELECT, 'createSelect');
}
public function testCreate2()
{
$this->prepareFactoryTest(
BoolFilterListApplier::class, ApplierFactory::BOOL_FILTER_LIST, 'createBoolFilterList');
}
public function testCreate3()
{
$this->prepareFactoryTest(TextFilterApplier::class, ApplierFactory::TEXT_FILTER, 'createTextFilter');
}
public function testCreate4()
{
$this->prepareFactoryTest(WhereApplier::class, ApplierFactory::WHERE, 'createWhere');
}
public function testCreate5()
{
$this->prepareFactoryTest(OrderApplier::class, ApplierFactory::ORDER, 'createOrder');
}
public function testCreate6()
{
$this->prepareFactoryTest(LimitApplier::class, ApplierFactory::LIMIT, 'createLimit');
}
public function testCreate7()
{
$this->prepareFactoryTest(AdditionalApplier::class, ApplierFactory::ADDITIONAL, 'createAdditional');
}
public function testCreate8()
{
$this->prepareFactoryTest(
PrimaryFilterApplier::class, ApplierFactory::PRIMARY_FILTER, 'createPrimaryFilter');
}
public function testCreate9()
{
$this->prepareFactoryTest(
AccessControlFilterApplier::class, ApplierFactory::ACCESS_CONTROL_FILTER, 'createAccessControlFilter');
}
protected function prepareFactoryTest(string $defaultClassName, string $type, string $method)
{
$entityType = 'Test';
$applierClassName = $className ?? $defaultClassName;
$applier = $this->createMock($defaultClassName);
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindInstance(User::class, $this->user)
->bindInstance(AclManager::class, $this->aclManager)
->bindInstance(Acl::class, $this->acl)
->for($applierClassName)
->bindValue('$entityType', $entityType);
$bindingContainer = new BindingContainer($bindingData);
$this->injectableFactory
->expects($this->once())
->method('createWithBinding')
->with($applierClassName, $bindingContainer)
->willReturn($applier);
$resultApplier = $this->factory->$method(
$entityType,
$this->user,
$type
);
$this->assertEquals($applier, $resultApplier);
}
}

View File

@@ -0,0 +1,145 @@
<?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\Select\Bool;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Select\Bool\FilterFactory as BoolFilterFactory;
use Espo\Core\Select\Bool\Filters\Followed;
use Espo\Core\Select\Helpers\FieldHelper;
use Espo\Core\Utils\Metadata;
use Espo\Entities\User;
use PHPUnit\Framework\TestCase;
class FilterFactoryTest extends TestCase
{
private $injectableFactory;
private $metadata;
private $user;
private $factory;
protected function setUp(): void
{
$this->injectableFactory = $this->createMock(InjectableFactory::class);
$this->metadata = $this->createMock(Metadata::class);
$this->user = $this->createMock(User::class);
$this->factory = new BoolFilterFactory(
$this->injectableFactory,
$this->metadata
);
}
public function testCreate1()
{
$this->prepareFactoryTest(null, Followed::class, 'followed');
}
public function testCreate2()
{
$this->prepareFactoryTest('SomeClass', Followed::class, 'followed');
}
protected function prepareFactoryTest(?string $className, string $defaultClassName, string $name)
{
$entityType = 'Test';
$this->metadata
->expects($this->any())
->method('get')
->willReturnMap([
[['selectDefs', $entityType, 'boolFilterClassNameMap', $name], null, $className],
]);
$className = $className ?? $defaultClassName;
$object = $this->createMock($defaultClassName);
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindInstance(User::class, $this->user)
->for($className)
->bindValue('$entityType', $entityType);
$binder
->for(FieldHelper::class)
->bindValue('$entityType', $entityType);
$bindingContainer = new BindingContainer($bindingData);
$this->injectableFactory
->expects($this->once())
->method('createWithBinding')
->with($className, $bindingContainer)
->willReturn($object);
$resultObject = $this->factory->create(
$entityType,
$this->user,
$name
);
$this->assertEquals($object, $resultObject);
$this->assertTrue(
$this->factory->has($entityType, $name)
);
$this->metadata
->expects(self::any())
->method('get')
->willReturnMap([
[
[
'selectDefs',
$entityType,
'boolFilterClassNameMap',
'badName',
],
null,
null
],
[
['app', 'select', 'boolFilterClassNameMap', 'badName'],
null,
null
],
]);
$this->assertFalse(
$this->factory->has($entityType, 'badName')
);
}
}

View File

@@ -0,0 +1,118 @@
<?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\Select\Bool;
use Espo\Core\Select\Bool\Filters\OnlyMy;
use Espo\Core\Select\Bool\Filters\Shared;
use Espo\Core\Select\Helpers\FieldHelper;
use Espo\Entities\User;
use Espo\ORM\Defs;
use Espo\ORM\Defs\EntityDefs;
use Espo\ORM\Defs\RelationDefs;
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
use Espo\ORM\Query\Select;
use Espo\ORM\Query\SelectBuilder;
use Espo\ORM\Type\RelationType;
use PHPUnit\Framework\TestCase;
class FiltersTest extends TestCase
{
public function testOnlyMyUsesMiddleConditions(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn('user-id');
$user->method('isPortal')->willReturn(false);
$fieldHelper = $this->createMock(FieldHelper::class);
$fieldHelper->method('hasAssignedUsersField')->willReturn(true);
$filter = new OnlyMy('Test', $user, $fieldHelper, $this->createDefs('assignedUsers', 'entityUser'));
$joinConditions = $this->applyAndGetJoinConditions($filter);
$this->assertEquals('Test', $joinConditions['assignedUsersMiddle.entityType'] ?? null);
}
public function testSharedUsesMiddleConditions(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn('user-id');
$fieldHelper = $this->createMock(FieldHelper::class);
$fieldHelper->method('hasCollaboratorsField')->willReturn(true);
$filter = new Shared('Test', $user, $fieldHelper, $this->createDefs('collaborators', 'entityCollaborator'));
$joinConditions = $this->applyAndGetJoinConditions($filter);
$this->assertEquals('Test', $joinConditions['collaboratorsMiddle.entityType'] ?? null);
}
private function createDefs(string $link, string $relationName): Defs
{
$relationDefs = RelationDefs::fromRaw([
'type' => RelationType::MANY_MANY,
'entity' => User::ENTITY_TYPE,
'relationName' => $relationName,
'midKeys' => ['entityId', 'userId'],
'conditions' => ['entityType' => 'Test'],
], $link);
$entityDefs = $this->createMock(EntityDefs::class);
$entityDefs->method('getRelation')->with($link)->willReturn($relationDefs);
$defs = $this->createMock(Defs::class);
$defs->method('getEntity')->with('Test')->willReturn($entityDefs);
return $defs;
}
/**
* @return array<string, mixed>
*/
private function applyAndGetJoinConditions(OnlyMy|Shared $filter): array
{
$orGroupBuilder = new OrGroupBuilder();
$filter->apply(SelectBuilder::create()->from('Test'), $orGroupBuilder);
$rawValue = $orGroupBuilder->build()->getRawValue();
$subQuery = $rawValue['id=s'] ?? null;
$this->assertInstanceOf(Select::class, $subQuery);
$joins = $subQuery->getRaw()['joins'] ?? [];
$this->assertCount(1, $joins);
return $joins[0][2];
}
}

Some files were not shown because too many files have changed in this diff Show More