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

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

View File

@@ -0,0 +1,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\integration\Espo\Account;
class ChangeFieldsTest extends \tests\integration\Core\BaseTestCase
{
protected ?string $userName = 'admin';
protected ?string $password = '1';
public function testChangeName()
{
$entityManager = $this->getContainer()->get('entityManager');
$account = $entityManager->getEntity('Account', '53203b942850b');
$this->assertEquals('Espo\\Modules\\Crm\\Entities\\Account', get_class($account));
$this->assertEquals('Besharp', $account->get('name'));
$account->set('name', 'Changed Name');
$entityManager->saveEntity($account);
$account = $entityManager->getEntity('Account', '53203b942850b');
$this->assertEquals('Changed Name', $account->get('name'));
}
}

View File

@@ -0,0 +1,57 @@
<?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\integration\Espo\Account;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
class CreateTest extends \tests\integration\Core\BaseTestCase
{
protected ?string $dataFile = 'Account/ChangeFields.php';
protected ?string $userName = 'admin';
protected ?string $password = '1';
public function testCreate()
{
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account');
$entity = $service->create((object) [
'name' => 'Test Account',
'emailAddress' => 'test@tester.com',
'phoneNumber' => '+14333633333',
], CreateParams::create());
$this->assertInstanceOf('Espo\\ORM\\Entity', $entity->getEntity());
$this->assertTrue(!empty($entity->getId()));
}
}

View File

@@ -0,0 +1,71 @@
<?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\integration\Espo\Account;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Select\SearchParams;
class SearchTest extends \tests\integration\Core\BaseTestCase
{
protected ?string $dataFile = 'Account/ChangeFields.php';
protected ?string $userName = 'admin';
protected ?string $password = '1';
public function testSearchByName(): void
{
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account');
$params = [
'where' => [
[
'type' => 'textFilter',
'value' => 'Besha',
],
],
'offset' => 0,
'maxSize' => 20,
'asc' => true,
'sortBy' => 'name',
];
$result = $service->find(SearchParams::fromRaw($params));
$this->assertEquals(1, $result->getTotal());
$this->assertInstanceOf('Espo\\ORM\\EntityCollection', $result->getCollection());
$list = $result->getCollection()->getValueMapList();
$this->assertEquals('53203b942850b', $list[0]->id);
}
}

View File

@@ -0,0 +1,367 @@
<?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\integration\Espo\Actions;
use Espo\Core\Action\Actions\Merge\Merger;
use Espo\Core\Action\Api\PostProcess;
use Espo\Core\Action\Params;
use Espo\Core\Application;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Field\EmailAddress;
use Espo\Core\Field\PhoneNumber;
use Espo\Core\ORM\EntityManager;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use tests\integration\Core\BaseTestCase;
class ActionTest extends BaseTestCase
{
/** @var EntityManager */
private $entityManager;
/** @var Merger */
private $merger;
private ?PostProcess $action = null;
private function init(): void
{
$this->entityManager = $this->getApplication()
->getContainer()
->getByClass(EntityManager::class);
$this->action = $this->getInjectableFactory()->create(PostProcess::class);
$this->merger = $this->getApplication()
->getInjectableFactory()
->create(Merger::class);
}
public function testActionNotAllowed(): void
{
$this->init();
$data = [
'entityType' => 'Role',
'action' => 'convertCurrency',
'id' => 'arbitrary-id',
'data' => (object) [],
];
$request = $this->createRequest(
'POST',
[],
['Content-Type' => 'application/json'],
json_encode($data)
);
$this->expectException(Forbidden::class);
$this->action->process($request);
}
public function testActionMerge1(): void
{
$this->createUser('tester', [
'assignmentPermission' => 'all',
'data' => [
'Contact' => [
'create' => 'all',
'read' => 'all',
'edit' => 'all',
'delete' => 'all',
],
],
]);
$this->authenticate('tester');
$this->init();
$team1 = $this->entityManager->createEntity('Team', []);
$team2 = $this->entityManager->createEntity('Team', []);
$account1 = $this->entityManager->createEntity('Account', []);
$account2 = $this->entityManager->createEntity('Account', []);
/* @var $contact1 Contact */
$contact1 = $this->entityManager->getNewEntity('Contact');
$emailAddressGroup1 = $contact1->getEmailAddressGroup()
->withAdded(EmailAddress::create('c1a@test.com'))
->withAdded(EmailAddress::create('c1b@test.com')->invalid());
$contact1->setEmailAddressGroup($emailAddressGroup1);
$phoneNumberGroup1 = $contact1->getPhoneNumberGroup()
->withAdded(PhoneNumber::create('+11000000000'))
->withAdded(PhoneNumber::create('+12000000000')->invalid());
$contact1->setPhoneNumberGroup($phoneNumberGroup1);
$contact1->set('accountId', $account1->getId());
$contact1->set('teamsIds', [$team1->getId()]);
/* @var $contact2 Contact */
$contact2 = $this->entityManager->getNewEntity('Contact');
$emailAddressGroup2 = $contact1->getEmailAddressGroup()
->withAdded(EmailAddress::create('c2a@test.com'))
->withAdded(EmailAddress::create('c2b@test.com')->optedOut());
$contact2->setEmailAddressGroup($emailAddressGroup2);
$phoneNumberGroup2 = $contact2->getPhoneNumberGroup()
->withAdded(PhoneNumber::create('+11100000000'))
->withAdded(PhoneNumber::create('+12100000000')->optedOut());
$contact2->setPhoneNumberGroup($phoneNumberGroup2);
$contact2->set('accountId', $account2->getId());
$contact2->set('teamsIds', [$team2->getId()]);
$this->entityManager->saveEntity($contact1);
$this->entityManager->saveEntity($contact2);
$this->entityManager->createEntity('Note', [
'type' => 'Post',
'parentType' => 'Contact',
'parentId' => $contact1->getId(),
]);
$note2 = $this->entityManager->createEntity('Note', [
'type' => 'Post',
'parentType' => 'Contact',
'parentId' => $contact2->getId(),
]);
$opportunity1 = $this->entityManager->createEntity('Opportunity', []);
$opportunity2 = $this->entityManager->createEntity('Opportunity', []);
$this->entityManager
->getRDBRepository('Contact')
->getRelation($contact1, 'opportunities')
->relate($opportunity1);
$this->entityManager
->getRDBRepository('Contact')
->getRelation($contact2, 'opportunities')
->relate($opportunity2);
$data = [
'entityType' => 'Contact',
'action' => 'merge',
'id' => $contact1->getId(),
'data' => (object) [
'attributes' => (object) [
'description' => 'merged',
],
'sourceIdList' => [$contact2->getId()],
],
];
$request = $this->createRequest(
'POST',
[],
['Content-Type' => 'application/json'],
json_encode($data)
);
$this->action->process($request);
/* @var $contact1Reloaded Contact */
$contact1Reloaded = $this->entityManager->getEntityById('Contact', $contact1->getId());
$contact2Reloaded = $this->entityManager->getEntityById('Contact', $contact2->getId());
$this->assertEquals('merged', $contact1Reloaded->get('description'));
$this->assertNull($contact2Reloaded);
$emailAddressGroup = $contact1Reloaded->getEmailAddressGroup();
$phoneNumberGroup = $contact1Reloaded->getPhoneNumberGroup();
$this->assertEquals(4, $emailAddressGroup->getCount());
$this->assertEquals(4, $phoneNumberGroup->getCount());
$this->assertEquals(
'c1a@test.com',
$emailAddressGroup->getPrimary()->getAddress()
);
$this->assertEquals(
'+11000000000',
$phoneNumberGroup->getPrimary()->getNumber()
);
$this->assertTrue(
$emailAddressGroup->getByAddress('c2b@test.com')->isOptedOut()
);
$this->assertTrue(
$emailAddressGroup->getByAddress('c1b@test.com')->isInvalid()
);
$this->assertTrue(
$phoneNumberGroup->getByNumber('+12100000000')->isOptedOut()
);
$this->assertTrue(
$phoneNumberGroup->getByNumber('+12000000000')->isInvalid()
);
$this->assertEquals(
$contact1->getId(),
$this->entityManager
->getEntityById('Note', $note2->getId())
->get('parentId')
);
$this->assertEquals(
2,
$this->entityManager
->getRDBRepository('Contact')
->getRelation($contact1Reloaded, 'opportunities')
->count()
);
$this->assertEquals(
2,
$this->entityManager
->getRDBRepository('Contact')
->getRelation($contact1Reloaded, 'teams')
->count()
);
$this->assertEquals(
2,
count($contact1Reloaded->getLinkMultipleIdList('accounts'))
);
}
public function testMergeRelationshipColumns(): void
{
$em = $this->getEntityManager();
$account = $em->getRDBRepositoryByClass(Account::class)->getNew();
$accountSource = $em->getRDBRepositoryByClass(Account::class)->getNew();
$contact = $em->getRDBRepositoryByClass(Contact::class)->getNew();
$em->saveEntity($account);
$em->saveEntity($accountSource);
$em->saveEntity($contact);
$em->getRelation($accountSource, 'contacts')
->relate($contact, [
'role' => 'Tester',
'isInactive' => true,
]);
$merger = $this->getInjectableFactory()->create(Merger::class);
/** @noinspection PhpUnhandledExceptionInspection */
$merger->process(
new Params(Account::ENTITY_TYPE, $account->getId()),
[$accountSource->getId()],
(object) []
);
$em->refreshEntity($account);
$relation = $em->getRelation($account, 'contacts');
$this->assertEquals(
'Tester',
$relation->getColumn($contact, 'role')
);
$this->assertTrue(
$relation->getColumn($contact, 'isInactive')
);
}
public function testMergeNoEditAccess(): void
{
$this->createUser('tester', [
'assignmentPermission' => 'all',
'data' => [
'Contact' => [
'create' => 'own',
'read' => 'all',
'edit' => 'own',
'delete' => 'no',
],
],
]);
$this->authenticate('tester');
$this->init();
$contact1 = $this->entityManager->createEntity('Contact', []);
$contact2 = $this->entityManager->createEntity('Contact', []);
$params = new Params('Contact', $contact1->getId());
$this->expectException(Forbidden::class);
$this->merger->process($params, [$contact2->getId()], (object) []);
}
public function testMergeNoDeleteAccess(): void
{
$this->createUser('tester', [
'assignmentPermission' => 'all',
'data' => [
'Contact' => [
'create' => 'own',
'read' => 'all',
'edit' => 'all',
'delete' => 'no',
],
],
]);
$this->authenticate('tester');
$this->init();
$contact1 = $this->entityManager->createEntity('Contact', []);
$contact2 = $this->entityManager->createEntity('Contact', []);
$params = new Params('Contact', $contact1->getId());
$this->expectException(Forbidden::class);
$this->merger->process($params, [$contact2->getId()], (object) []);
}
}

View File

@@ -0,0 +1,349 @@
<?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\integration\Espo\Actions;
use Espo\Core\MassAction\Api\PostProcess;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Core\MassAction\ServiceParams;
use Espo\Core\MassAction\Service;
use Espo\Core\MassAction\Params as MassActionParams;
use Espo\Core\MassAction\Jobs\Process as JobProcess;
use Espo\Core\Job\Job\Data as JobData;
use Espo\Core\Select\SearchParams;
use Espo\Core\Api\ResponseWrapper;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\ORM\EntityManager;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Opportunity;
use Espo\Tools\MassUpdate\Data as MassUpdateData;
use Espo\Tools\MassUpdate\Processor;
use RuntimeException;
use tests\integration\Core\BaseTestCase;
class MassActionTest extends BaseTestCase
{
/** @var EntityManager */
private $entityManager;
private ?PostProcess $action = null;
private function init(): void
{
$this->entityManager = $this->getApplication()
->getContainer()
->getByClass(EntityManager::class);
$this->action = $this->getInjectableFactory()->create(PostProcess::class);
}
private function getAdminUser(): User
{
$repository = $this->getContainer()
->getByClass(EntityManager::class)
->getRDBRepositoryByClass(User::class);
$user = $repository
->where(['type' => User::TYPE_ADMIN])
->findOne();
if (!$user) {
$user = $repository->getNew();
$user->set('userName', 'test-admin');
$user->set('type', User::TYPE_ADMIN);
$repository->save($user);
}
return $user;
}
public function testUpdate1(): void
{
$adminUser = $this->getAdminUser();
$this->createUser([
'userName' => 'admin-test',
'type' => 'admin',
]);
$this->authenticate('admin-test');
$this->init();
$account = $this->entityManager->createEntity('Account', [
'name' => 'test',
]);
$data = [
'entityType' => 'Account',
'action' => 'update',
'params' => [
'ids' => [$account->getId()],
],
'data' => [
'assignedUserId' => $adminUser->getId(),
],
];
$request = $this->createRequest(
'POST',
[],
['Content-Type' => 'application/json'],
json_encode($data)
);
$this->action->process($request);
$accountReloaded = $this->entityManager->getEntityById('Account', $account->getId());
$this->assertEquals($adminUser->getId(), $accountReloaded->get('assignedUserId'));
}
public function testUpdateNotAllowed(): void
{
$this->init();
$data = [
'entityType' => 'Role',
'action' => 'update',
'params' => [
'ids' => ['arbitrary-id'],
],
'data' => [
'name' => '1'
],
];
$request = $this->createRequest(
'POST',
[],
['Content-Type' => 'application/json'],
json_encode($data)
);
$this->createMock(ResponseWrapper::class);
$this->expectException(Forbidden::class);
$this->action->process($request);
}
public function testAcl1(): void
{
$em = $this->getEntityManager();
$user = $this->createUser('tester', [
'massUpdatePermission' => 'yes',
'data' => [
'Account' => [
'create' => 'no',
'read' => 'all',
'edit' => 'own',
'delete' => 'no',
],
],
]);
$account1 = $em->createEntity(Account::ENTITY_TYPE, [
'name' => '1',
'assignedUserId' => $user->getId(),
]);
$account2 = $em->createEntity(Account::ENTITY_TYPE, [
'name' => '2',
]);
$this->authenticate('tester');
$injectableFactory = $this->getInjectableFactory();
$this->assertEquals($user->getId(), $this->getContainer()->getByClass(User::class)->getId());
/** @var Service $service */
$service = $injectableFactory->create(Service::class);
$serviceParams = ServiceParams
::create(
MassActionParams::createWithSearchParams('Account', SearchParams::create())
)
->withIsIdle();
$serviceResult = $service->process(
'Account',
'update',
$serviceParams,
(object) [
'type' => 'Customer',
]
);
$this->assertFalse($serviceResult->hasResult());
$massActionId = $serviceResult->getId();
$this->authenticate(null);
$this->assertEquals('system', $this->getContainer()->getByClass(User::class)->getId());
$injectableFactory1 = $this->getInjectableFactory();
$process = $injectableFactory1->create(JobProcess::class);
$process->run(JobData::create()->withTargetId($massActionId));
$em->refreshEntity($account1);
$em->refreshEntity($account2);
$this->assertEquals('Customer', $account1->get('type'));
$this->assertEquals(null, $account2->get('type'));
}
public function testAcl2()
{
$user = $this->createUser('tester', [
'massUpdatePermission' => 'yes',
'data' => [
'User' => [
'create' => 'no',
'read' => 'all',
'edit' => 'own',
'delete' => 'no',
],
],
]);
$this->createUser('tester-1', []);
$this->authenticate('tester');
$injectableFactory = $this->getInjectableFactory();
$this->assertEquals($user->getId(), $this->getContainer()->getByClass(User::class)->getId());
$service = $injectableFactory->create(Service::class);
$serviceParams = ServiceParams
::create(
MassActionParams::createWithSearchParams('User', SearchParams::create())
)
->withIsIdle();
$serviceResult = $service->process(
'User',
'update',
$serviceParams,
(object) [
'title' => 'Tester',
]
);
$this->assertFalse($serviceResult->hasResult());
$massActionId = $serviceResult->getId();
$this->authenticate(null);
$this->assertEquals('system', $this->getContainer()->getByClass(User::class)->getId());
$injectableFactory1 = $this->getInjectableFactory();
$process = $injectableFactory1->create(JobProcess::class);
// Mass-update for User entity type is allowed only for admins.
$this->expectException(RuntimeException::class);
$process->run(JobData::create()->withTargetId($massActionId));
}
public function testMassUpdateLinkAccess(): void
{
$user = $this->createUser('tester', [
'massUpdatePermission' => 'yes',
'data' => [
'Contact' => [
'create' => 'no',
'read' => 'own',
'edit' => 'no',
'delete' => 'no'
],
'Opportunity' => [
'create' => 'yes',
'read' => 'team',
'edit' => 'own',
'delete' => 'own'
],
],
]);
$this->authenticate('tester');
$contact1 = $this->getEntityManager()
->createEntity(Contact::ENTITY_TYPE, [
'lastName' => 'Contact 1',
'assignedUserId' => $user->getId(),
]);
$contact2 = $this->getEntityManager()
->createEntity(Contact::ENTITY_TYPE, [
'lastName' => 'Contact 2',
]);
$opp1 = $this->getEntityManager()
->createEntity(Opportunity::ENTITY_TYPE, [
'name' => 'Opp 1',
'assignedUserId' => $user->getId(),
]);
$opp2 = $this->getEntityManager()
->createEntity(Opportunity::ENTITY_TYPE, [
'name' => 'Opp 2',
'assignedUserId' => $user->getId(),
]);
$processor = $this->getInjectableFactory()->create(Processor::class);
$params = MassActionParams::createWithIds(Opportunity::ENTITY_TYPE, [$opp1->getId(), $opp2->getId()]);
$data = MassUpdateData::create()
->with('contactsIds', [$contact1->getId()]);
$result = $processor->process($params, $data);
$this->assertEquals(2, $result->getCount());
$data = MassUpdateData::create()
->with('contactsIds', [$contact2->getId()]);
$result = $processor->process($params, $data);
$this->assertEquals(0, $result->getCount());
}
}

View File

@@ -0,0 +1,101 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Attachment;
class RemoveDuplicateTest extends \tests\integration\Core\BaseTestCase
{
public function testRemoveDuplicate()
{
$entityManager = $this->getContainer()->get('entityManager');
$fileStorageManager = $this->getContainer()->get('fileStorageManager');
$attachment = $entityManager->getEntity('Attachment');
$attachment->set([
'name' => 'test.txt',
'contents' => 'Hello Test'
]);
$entityManager->saveEntity($attachment);
$copy = $entityManager->getRepository('Attachment')->getCopiedAttachment($attachment);
$entityManager->removeEntity($copy);
$this->assertTrue($fileStorageManager->exists($attachment));
}
public function testRemoveOriginal()
{
$entityManager = $this->getContainer()->get('entityManager');
$fileStorageManager = $this->getContainer()->get('fileStorageManager');
$attachment = $entityManager->getEntity('Attachment');
$attachment->set([
'name' => 'test.txt',
'contents' => 'Hello Test'
]);
$entityManager->saveEntity($attachment);
$copy = $entityManager->getRepository('Attachment')->getCopiedAttachment($attachment);
$entityManager->removeEntity($attachment);
$this->assertTrue($fileStorageManager->exists($copy));
}
public function testRemoveOriginalAndDuplicate()
{
$entityManager = $this->getContainer()->get('entityManager');
$fileStorageManager = $this->getContainer()->get('fileStorageManager');
$attachment = $entityManager->getEntity('Attachment');
$attachment->set([
'name' => 'test.txt',
'contents' => 'Hello Test'
]);
$entityManager->saveEntity($attachment);
$copy = $entityManager->getRepository('Attachment')->getCopiedAttachment($attachment);
$entityManager->removeEntity($attachment);
$entityManager->removeEntity($copy);
$this->assertFalse($fileStorageManager->exists($copy));
}
}

View File

@@ -0,0 +1,54 @@
<?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 integration\Espo\Commands;
use Espo\Classes\ConsoleCommands\AppCheck;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use tests\integration\Core\BaseTestCase;
class AppCheckTest extends BaseTestCase
{
public function testNotFailed(): void
{
$command = $this->getInjectableFactory()->create(AppCheck::class);
$params = $this->createMock(Params::class);
$io = $this->createMock(IO::class);
$io->expects($this->never())
->method('setExitStatus')
->with(1);
$command->run($params, $io);
$this->assertEquals(0, $io->getExitStatus());
}
}

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\integration\Espo\Core\Acl;
use Espo\Core\AclManager;
use Espo\Core\Acl;
use Espo\Core\Field\LinkMultiple;
use Espo\Entities\Role;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Call;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\Modules\Crm\Entities\Task;
use RuntimeException;
use tests\integration\Core\BaseTestCase;
class AclTest extends BaseTestCase
{
public function testGetReadOwnerUserField()
{
$aclManager = $this->getContainer()->getByClass(AclManager::class);
$this->assertEquals(
'assignedUser',
$aclManager->getReadOwnerUserField('Account')
);
$this->assertEquals(
'createdBy',
$aclManager->getReadOwnerUserField('Import')
);
$this->assertEquals(
'users',
$aclManager->getReadOwnerUserField('Email')
);
$this->assertEquals(
'users',
$aclManager->getReadOwnerUserField('Meeting')
);
}
public function testTryCheck(): void
{
/* @var $Acl Acl */
$acl = $this->getContainer()->get('acl');
$this->assertFalse($acl->tryCheck('NonExistingEntityType'));
}
public function testCheckScopeAccess(): void
{
$this->createUser('tester', [
'data' => [
Task::ENTITY_TYPE => false,
Call::ENTITY_TYPE => [
'create' => Acl\Table::LEVEL_NO,
'read' => Acl\Table::LEVEL_NO,
'edit' => Acl\Table::LEVEL_NO,
'delete' => Acl\Table::LEVEL_NO,
],
Meeting::ENTITY_TYPE => [
'create' => Acl\Table::LEVEL_YES,
'read' => Acl\Table::LEVEL_YES,
'edit' => Acl\Table::LEVEL_NO,
'delete' => Acl\Table::LEVEL_NO,
],
],
]);
$this->authenticate('tester');
$acl = $this->getContainer()->getByClass(Acl::class);
$this->assertFalse($acl->checkScope(Task::ENTITY_TYPE));
$this->assertTrue($acl->checkScope(Call::ENTITY_TYPE));
$this->assertTrue($acl->checkScope(Meeting::ENTITY_TYPE));
$this->assertFalse($acl->check(Task::ENTITY_TYPE, Acl\Table::ACTION_READ));
$this->assertFalse($acl->check(Call::ENTITY_TYPE, Acl\Table::ACTION_READ));
$this->assertTrue($acl->check(Meeting::ENTITY_TYPE, Acl\Table::ACTION_READ));
}
public function testCheckField(): void
{
$user = $this->createUser('tester', [
'fieldData' => [
'Call' => [
'direction' => [
'read' => Acl\Table::LEVEL_NO,
'edit' => Acl\Table::LEVEL_NO,
],
'contacts' => [
'read' => Acl\Table::LEVEL_YES,
'edit' => Acl\Table::LEVEL_NO,
],
],
],
]);
/** @var User $user */
$user = $this->getEntityManager()->getEntityById(User::ENTITY_TYPE, $user->getId());
$aclManager = $this->getContainer()->getByClass(AclManager::class);
$this->assertFalse($aclManager->checkField($user, 'Call', 'direction'));
$this->assertTrue($aclManager->checkField($user, 'Call', 'contacts'));
$this->assertFalse($aclManager->checkField($user, 'Call', 'contacts', Acl\Table::ACTION_EDIT));
}
public function testDisabledField(): void
{
$metadata = $this->getMetadata();
$metadata->set('entityDefs', 'Account', [
'fields' => [
'assignedUser' => [
'disabled' => true,
]
]
]);
$metadata->save();
$this->reCreateApplication(reuse: true);
$acl = $this->getContainer()->getByClass(Acl::class);
$this->assertFalse($acl->checkField(Account::ENTITY_TYPE, 'assignedUser'));
$this->assertTrue($acl->checkField(Account::ENTITY_TYPE, 'name'));
}
public function testDisabledLink(): void
{
$metadata = $this->getMetadata();
$metadata->set('entityDefs', 'Account', [
'links' => [
'opportunities' => [
'disabled' => true,
]
]
]);
$metadata->save();
$this->reCreateApplication(reuse: true);
$acl = $this->getContainer()->getByClass(Acl::class);
$this->assertFalse($acl->checkLink('Account', 'opportunities'));
}
public function testMultipleRoles(): void
{
$em = $this->getEntityManager();
$role1 = $em->getRDBRepositoryByClass(Role::class)->getNew();
$role1->setRawData([
'Lead' => [
Acl\Table::ACTION_CREATE => Acl\Table::LEVEL_YES,
],
'Opportunity' => [
Acl\Table::ACTION_EDIT => Acl\Table::LEVEL_NO,
],
'Account' => [
Acl\Table::ACTION_CREATE => Acl\Table::LEVEL_NO,
],
'Meeting' => [
Acl\Table::ACTION_CREATE => Acl\Table::LEVEL_YES,
Acl\Table::ACTION_EDIT => Acl\Table::LEVEL_NO,
Acl\Table::ACTION_STREAM => Acl\Table::LEVEL_NO,
],
'Call' => [
Acl\Table::ACTION_CREATE => Acl\Table::LEVEL_NO,
Acl\Table::ACTION_EDIT => Acl\Table::LEVEL_ALL,
],
]);
$em->saveEntity($role1);
$role2 = $em->getRDBRepositoryByClass(Role::class)->getNew();
$role2->setRawData([
'Lead' => [
Acl\Table::ACTION_EDIT => Acl\Table::LEVEL_NO,
],
'Opportunity' => [
Acl\Table::ACTION_CREATE => Acl\Table::LEVEL_YES,
],
'Account' => [
Acl\Table::ACTION_EDIT => Acl\Table::LEVEL_NO,
],
'Meeting' => [
Acl\Table::ACTION_CREATE => Acl\Table::LEVEL_NO,
Acl\Table::ACTION_EDIT => Acl\Table::LEVEL_ALL,
Acl\Table::ACTION_STREAM => Acl\Table::LEVEL_NO,
],
'Call' => [
Acl\Table::ACTION_CREATE => Acl\Table::LEVEL_YES,
Acl\Table::ACTION_EDIT => Acl\Table::LEVEL_NO,
],
]);
$em->saveEntity($role2);
$user = $em->getRDBRepositoryByClass(User::class)->getNew();
$user
->setUserName('test-1')
->setRoles(LinkMultiple::create()->withAddedIdList([$role1->getId(), $role2->getId()]));
$em->saveEntity($user);
$em->refreshEntity($user);
$aclManager = $this->getContainer()->getByClass(AclManager::class);
$this->assertTrue($aclManager->checkScope($user, 'Lead', Acl\Table::ACTION_CREATE));
$this->assertTrue($aclManager->checkScope($user, 'Opportunity', Acl\Table::ACTION_CREATE));
$this->assertFalse($aclManager->checkScope($user, 'Account', Acl\Table::ACTION_CREATE));
$this->assertFalse($aclManager->checkScope($user, 'Opportunity', Acl\Table::ACTION_EDIT));
$this->assertFalse($aclManager->checkScope($user, 'Account', Acl\Table::ACTION_EDIT));
$this->assertTrue($aclManager->checkScope($user, 'Meeting', Acl\Table::ACTION_CREATE));
$this->assertTrue($aclManager->checkScope($user, 'Meeting', Acl\Table::ACTION_EDIT));
$this->assertFalse($aclManager->checkScope($user, 'Meeting', Acl\Table::ACTION_STREAM));
$this->assertTrue($aclManager->checkScope($user, 'Call', Acl\Table::ACTION_CREATE));
$this->assertTrue($aclManager->checkScope($user, 'Call', Acl\Table::ACTION_EDIT));
}
public function testCheckAdmin(): void
{
$user = $this->createUser(
userData: [
User::FIELD_USER_NAME => 'test',
],
);
$admin = $this->createUser(
userData: [
User::FIELD_USER_NAME => 'admin-test',
User::FIELD_TYPE => User::TYPE_ADMIN,
],
);
$aclManager = $this->getInjectableFactory()->create(AclManager::class);
//
$user = $this->reFetchUser($user);
$admin = $this->reFetchUser($admin);
$this->assertFalse($aclManager->checkAdmin($user));
$this->assertTrue($aclManager->checkAdmin($admin));
$this->assertFalse($aclManager->createUserAcl($user)->checkAdmin());
$this->assertTrue($aclManager->createUserAcl($admin)->checkAdmin());
//
$user = $this->reFetchUser($user);
$admin = $this->reFetchUser($admin);
$user->setScopes([Acl\Scope::ADMIN]);
$admin->setScopes([Acl\Scope::ADMIN]);
$this->assertFalse($aclManager->checkAdmin($user));
$this->assertTrue($aclManager->checkAdmin($admin));
//
$user = $this->reFetchUser($user);
$admin = $this->reFetchUser($admin);
$user->setScopes([]);
$admin->setScopes([]);
$this->assertFalse($aclManager->checkAdmin($user));
$this->assertFalse($aclManager->checkAdmin($admin));
}
private function reFetchUser(User $user): User
{
return $this->getEntityManager()
->getRDBRepositoryByClass(User::class)
->getById($user->getId()) ?? throw new RuntimeException();
}
}

View File

@@ -0,0 +1,247 @@
<?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 integration\Espo\Core\Acl;
use Espo\Core\Acl\Scope;
use Espo\Core\Acl\Table;
use Espo\Entities\Role;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Tools\Stream\GlobalRecordService;
use RuntimeException;
use tests\integration\Core\BaseTestCase;
class TableTest extends BaseTestCase
{
public function testScopesRegular(): void
{
$roleData = [
Account::ENTITY_TYPE => [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_ALL,
Table::ACTION_EDIT => Table::LEVEL_ALL,
Table::ACTION_DELETE => Table::LEVEL_ALL,
Table::ACTION_STREAM => Table::LEVEL_ALL,
],
Contact::ENTITY_TYPE => false,
Lead::ENTITY_TYPE => [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_ALL,
Table::ACTION_EDIT => Table::LEVEL_ALL,
Table::ACTION_DELETE => Table::LEVEL_ALL,
Table::ACTION_STREAM => Table::LEVEL_ALL,
],
GlobalRecordService::SCOPE_NAME => true,
];
$user = $this->createUser(
userData: [
User::FIELD_USER_NAME => 'test',
],
role: [
Role::FIELD_DATA => $roleData,
],
);
$factory = $this->getInjectableFactory()->create(Table\DefaultTableFactory::class);
//
$user = $this->reFetchUser($user);
$table = $factory->create($user);
$this->assertEquals(Table::LEVEL_YES, $table->getScopeData(Account::ENTITY_TYPE)->getCreate());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getRead());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getEdit());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getDelete());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getStream());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Lead::ENTITY_TYPE)->getRead());
$this->assertFalse($table->getScopeData(Contact::ENTITY_TYPE)->isTrue());
$this->assertTrue($table->getScopeData(GlobalRecordService::SCOPE_NAME)->isTrue());
//
$user->setScopes([
Lead::ENTITY_TYPE,
GlobalRecordService::SCOPE_NAME,
]);
$table = $factory->create($user);
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getCreate());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getRead());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getEdit());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getDelete());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getStream());
$this->assertEquals(Table::LEVEL_YES, $table->getScopeData(Lead::ENTITY_TYPE)->getCreate());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Lead::ENTITY_TYPE)->getRead());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Lead::ENTITY_TYPE)->getEdit());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Lead::ENTITY_TYPE)->getDelete());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Lead::ENTITY_TYPE)->getStream());
$this->assertTrue($table->getScopeData(GlobalRecordService::SCOPE_NAME)->isTrue());
//
$user = $this->reFetchUser($user);
$user->setScopes([
Scope::GLOBAL,
]);
$table = $factory->create($user);
$this->assertEquals(Table::LEVEL_YES, $table->getScopeData(Account::ENTITY_TYPE)->getCreate());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getRead());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getEdit());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getDelete());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getStream());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Lead::ENTITY_TYPE)->getRead());
$this->assertFalse($table->getScopeData(Contact::ENTITY_TYPE)->isTrue());
$this->assertTrue($table->getScopeData(GlobalRecordService::SCOPE_NAME)->isTrue());
//
$user = $this->reFetchUser($user);
$user->setScopes([]);
$table = $factory->create($user);
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getCreate());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getRead());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getEdit());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getDelete());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Account::ENTITY_TYPE)->getStream());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Lead::ENTITY_TYPE)->getCreate());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Lead::ENTITY_TYPE)->getRead());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Lead::ENTITY_TYPE)->getEdit());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Lead::ENTITY_TYPE)->getDelete());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Lead::ENTITY_TYPE)->getStream());
$this->assertFalse($table->getScopeData(Contact::ENTITY_TYPE)->isTrue());
$this->assertFalse($table->getScopeData(GlobalRecordService::SCOPE_NAME)->isTrue());
//
$user = $this->reFetchUser($user);
$user->setScopes([
Scope::ADMIN,
]);
$table = $factory->create($user);
$this->assertFalse($table->getScopeData(Scope::ADMIN)->isTrue());
$this->assertFalse($user->isEffectiveAdmin());
}
public function testScopesAdmin(): void
{
$user = $this->createUser(
userData: [
User::FIELD_USER_NAME => 'test',
User::FIELD_TYPE => User::TYPE_ADMIN,
]
);
$factory = $this->getInjectableFactory()->create(Table\DefaultTableFactory::class);
//
$user = $this->reFetchUser($user);
$table = $factory->create($user);
$this->assertTrue($table->getScopeData(Scope::ADMIN)->isTrue());
//
$user = $this->reFetchUser($user);
$user->setScopes([
Scope::ADMIN,
]);
$table = $factory->create($user);
$this->assertTrue($table->getScopeData(Scope::ADMIN)->isTrue());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getRead());
$this->assertTrue($table->getScopeData(GlobalRecordService::SCOPE_NAME)->isTrue());
//
$user = $this->reFetchUser($user);
$user->setScopes([
Scope::GLOBAL,
]);
$table = $factory->create($user);
$this->assertFalse($table->getScopeData(Scope::ADMIN)->isTrue());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getRead());
$this->assertTrue($table->getScopeData(GlobalRecordService::SCOPE_NAME)->isTrue());
//
$user = $this->reFetchUser($user);
$user->setScopes([
Account::ENTITY_TYPE,
]);
$table = $factory->create($user);
$this->assertFalse($table->getScopeData(Scope::ADMIN)->isTrue());
$this->assertEquals(Table::LEVEL_ALL, $table->getScopeData(Account::ENTITY_TYPE)->getRead());
$this->assertEquals(Table::LEVEL_NO, $table->getScopeData(Lead::ENTITY_TYPE)->getRead());
}
private function reFetchUser(User $user): User
{
return $this->getEntityManager()
->getRDBRepositoryByClass(User::class)
->getById($user->getId()) ?? throw new RuntimeException();
}
}

View File

@@ -0,0 +1,122 @@
<?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\integration\Espo\Core\Authentication\AuthToken;
use Espo\Core\Authentication\AuthToken\Data;
use Espo\Core\Authentication\AuthToken\Manager;
use tests\integration\Core\BaseTestCase;
class AuthTokenManagerTest extends BaseTestCase
{
public function testCreateWithSecret()
{
$authTokenManager = $this->getContainer()->getByClass(Manager::class);
$authTokenData = Data::create([
'passwordVersion' => 1,
'ipAddress' => 'ip-address',
'userId' => 'user-id',
'portalId' => 'portal-id',
'createSecret' => true,
]);
$authToken = $authTokenManager->create($authTokenData);
$this->assertEquals($authTokenData->getPasswordVersion(), $authToken->getPasswordVersion());
$this->assertEquals($authTokenData->getUserId(), $authToken->getUserId());
$this->assertEquals($authTokenData->getPortalId(), $authToken->getPortalId());
$this->assertTrue($authToken->isActive());
$this->assertNotEmpty($authToken->getToken());
$this->assertNotEmpty($authToken->getSecret());
$this->assertNotEmpty($authToken->get('lastAccess'));
}
public function testCreateWithNoSecretNoPortal()
{
$authTokenManager = $this->getContainer()->getByClass(Manager::class);
$authTokenData = Data::create([
'passwordVersion' => 1,
'ipAddress' => 'ip-address',
'userId' => 'user-id',
'portalId' => null,
'createSecret' => false,
]);
$authToken = $authTokenManager->create($authTokenData);
$this->assertEquals($authTokenData->getPasswordVersion(), $authToken->getPasswordVersion());
$this->assertEquals($authTokenData->getUserId(), $authToken->getUserId());
$this->assertEmpty($authToken->getPortalId());
$this->assertNotEmpty($authToken->getToken());
$this->assertEmpty($authToken->getSecret());
}
public function testRenew()
{
$authTokenManager = $this->getContainer()->getByClass(Manager::class);
$authTokenData = Data::create([
'passwordVersion' => 1,
'userId' => 'user-id',
]);
$authToken = $authTokenManager->create($authTokenData);
$authToken = $authTokenManager->get($authToken->getToken());
$authTokenManager->renew($authToken);
$this->assertNotEmpty($authToken->get('lastAccess'));
}
public function testInactivate()
{
$authTokenManager = $this->getContainer()->getByClass(Manager::class);
$authTokenData = Data::create([
'passwordVersion' => 1,
'userId' => 'user-id',
]);
$authToken = $authTokenManager->create($authTokenData);
$authToken = $authTokenManager->get($authToken->getToken());
$authTokenManager->inactivate($authToken);
$this->assertFalse($authToken->isActive());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
<?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\integration\Espo\Core\Authentication;
use Espo\Core\Api\RequestWrapper;
use Espo\Core\Api\Response;
use Espo\Core\Authentication\Authentication;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Authentication\HeaderKey;
use Espo\Core\Authentication\Util\DelayUtil;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingProcessor;
use Espo\Core\Utils\Config\ConfigWriter;
use Slim\Psr7\Factory\ServerRequestFactory;
use tests\integration\Core\BaseTestCase;
class FailedLoginAttemptsTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testUsernameFailedLogin(): void
{
$delay = 5;
$configWriter = $this->getInjectableFactory()->create(ConfigWriter::class);
$configWriter->setMultiple([
'authUsernameFailedAttemptsLimitEnabled' => true,
'authUsernameFailedAttemptsDelay' => $delay,
'authMaxUsernameFailedAttemptNumber' => 3,
]);
$configWriter->save();
$delayUtil = $this->createMock(DelayUtil::class);
$app = $this->createApplication(
binding: new class ($delayUtil) implements BindingProcessor {
public function __construct(private DelayUtil $delayUtil) {}
public function process(Binder $binder): void
{
$binder->bindInstance(DelayUtil::class, $this->delayUtil);
}
},
reuse: true,
);
$this->setApplication($app);
$delayUtil->expects($this->once())
->method('delay')
->with($delay * 1000);
$username = 'test';
$data = AuthenticationData::create()
->withUsername($username)
->withPassword('1');
$time = microtime(true);
$authentication = $this->getInjectableFactory()->create(Authentication::class);
$request = $this->createApiRequest($time, '1.0.0.1', $username);
$response = $this->createMock(Response::class);
$authentication->login($data, $request, $response);
$request = $this->createApiRequest($time, '1.0.0.2', $username);
$response = $this->createMock(Response::class);
$authentication->login($data, $request, $response);
$request = $this->createApiRequest($time, '1.0.0.3', $username);
$response = $this->createMock(Response::class);
$authentication->login($data, $request, $response);
$request = $this->createApiRequest($time, '1.0.0.4', $username);
$response = $this->createMock(Response::class);
$authentication->login($data, $request, $response);
}
private function createApiRequest(float $time, string $ipAddress, string $username): RequestWrapper
{
$authorization = 'Basic ' . base64_encode($username . ':1');
$request = (new ServerRequestFactory())->createServerRequest('POST', 'http://localhost/api/v1/App/user', [
'REMOTE_ADDR' => $ipAddress,
'REQUEST_TIME_FLOAT' => $time,
]);
$request = $request->withHeader(HeaderKey::AUTHORIZATION, $authorization);
return new RequestWrapper($request);
}
}

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 integration\Espo\Core\Authentication\Oidc;
use Espo\Core\Authentication\Oidc\UserProvider\DefaultUserInfoPopulator;
use Espo\Core\Authentication\Oidc\UserProvider\UserInfo;
use Espo\Core\Field\EmailAddress;
use Espo\Core\Field\EmailAddressGroup;
use Espo\Core\Field\PhoneNumber;
use Espo\Core\Field\PhoneNumberGroup;
use Espo\Entities\User;
use tests\integration\Core\BaseTestCase;
class UserInfoPopulatorTest extends BaseTestCase
{
public function testPopulate(): void
{
$em = $this->getEntityManager();
$user = $em->getRDBRepositoryByClass(User::class)->getNew();
$user
->setUserName('name')
->setPhoneNumberGroup(
PhoneNumberGroup::create([
PhoneNumber::create('+100'),
PhoneNumber::create('+200'),
])
)
->setEmailAddressGroup(
EmailAddressGroup::create([
EmailAddress::create('test1@test.com'),
EmailAddress::create('test2@test.com'),
]),
);
$em->saveEntity($user);
$userInfo = $this->createMock(UserInfo::class);
$userInfo
->method('get')
->willReturnMap([
['email', 'test3@test.com'],
['phone_number', '+300'],
]);
$populator = $this->getInjectableFactory()->create(DefaultUserInfoPopulator::class);
//
$populator->populate($userInfo, $user);
$em->saveEntity($user);
$em->refreshEntity($user);
$this->assertCount(2, $user->getEmailAddressGroup()->getList());
$this->assertCount(2, $user->getPhoneNumberGroup()->getList());
$this->assertEquals('test3@test.com', $user->getEmailAddress());
$this->assertEquals('+300', $user->getPhoneNumber());
$this->assertEquals('test2@test.com', $user->getEmailAddressGroup()->getList()[1]->getAddress());
$this->assertEquals('+200', $user->getPhoneNumberGroup()->getList()[1]->getNumber());
//
$userInfo = $this->createMock(UserInfo::class);
$userInfo
->method('get')
->willReturnMap([
['email', null],
['phone_number', null],
]);
$populator->populate($userInfo, $user);
$em->saveEntity($user);
$em->refreshEntity($user);
$this->assertEquals('test2@test.com', $user->getEmailAddress());
$this->assertEquals('+200', $user->getPhoneNumber());
$this->assertCount(1, $user->getEmailAddressGroup()->getList());
$this->assertCount(1, $user->getPhoneNumberGroup()->getList());
}
}

View File

@@ -0,0 +1,329 @@
<?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\integration\Espo\Core\Binding;
use Espo\Core\Application\ApplicationParams;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\Binding;
use Espo\Core\Binding\BindingData;
use Espo\Core\Binding\BindingLoader;
use Espo\Core\Binding\Key\QualifiedClassKey;
use Espo\Core\Container\ContainerBuilder;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\Cache\FileCacheItemPool;
use Psr\Cache\CacheItemPoolInterface;
use tests\integration\Core\BaseTestCase;
use tests\integration\testClasses\Binding\SomeClass;
use tests\integration\testClasses\Binding\SomeClassRequiringService;
use tests\integration\testClasses\Binding\SomeClassRequiringServiceAlt;
use tests\integration\testClasses\Binding\SomeClassRequiringValue;
use tests\integration\testClasses\Binding\SomeFactory;
use tests\integration\testClasses\Binding\SomeImplementation;
use tests\integration\testClasses\Binding\SomeInterface;
use tests\integration\testClasses\Binding\SomeService;
class BindingTest extends BaseTestCase
{
public function testImplementation(): void
{
$bindingLoader = new class() implements BindingLoader
{
public function load(): BindingData
{
$data = new BindingData();
$binder = new Binder($data);
$binder->bindImplementation(
CacheItemPoolInterface::class,
FileCacheItemPool::class
);
$data->addGlobal(
SomeInterface::class,
Binding::createFromImplementationClassName(
SomeImplementation::class
)
);
return $data;
}
};
$container = (new ContainerBuilder())
->withBindingLoader($bindingLoader)
->withParams(new ApplicationParams(noErrorHandler: true))
->build();
$injectableFactory = $container->get('injectableFactory');
$this->assertInstanceOf(InjectableFactory::class, $injectableFactory);
$obj = $injectableFactory->create(SomeClass::class);
$this->assertNotNull($obj);
$this->assertInstanceOf(
SomeImplementation::class,
$obj->get()
);
}
public function testFactory(): void
{
$bindingLoader = new class() implements BindingLoader
{
public function load(): BindingData
{
$data = new BindingData();
$binder = new Binder($data);
$binder->bindImplementation(
CacheItemPoolInterface::class,
FileCacheItemPool::class
);
$data->addGlobal(
SomeInterface::class,
Binding::createFromFactoryClassName(
SomeFactory::class
)
);
return $data;
}
};
$container = (new ContainerBuilder())
->withBindingLoader($bindingLoader)
->withParams(new ApplicationParams(noErrorHandler: true))
->build();
$injectableFactory = $container->get('injectableFactory');
$this->assertInstanceOf(InjectableFactory::class, $injectableFactory);
$obj = $injectableFactory->create(SomeClass::class);
$this->assertNotNull($obj);
$this->assertInstanceOf(
SomeImplementation::class,
$obj->get()
);
}
public function testCallback()
{
$bindingLoader = new class() implements BindingLoader
{
public function load(): BindingData
{
$data = new BindingData();
$binder = new Binder($data);
$binder->bindImplementation(
CacheItemPoolInterface::class,
FileCacheItemPool::class
);
$data->addGlobal(
SomeInterface::class,
Binding::createFromCallback(
function (SomeImplementation $some) {
return $some;
}
)
);
return $data;
}
};
$container = (new ContainerBuilder())
->withBindingLoader($bindingLoader)
->withParams(new ApplicationParams(noErrorHandler: true))
->build();
$injectableFactory = $container->get('injectableFactory');
$this->assertInstanceOf(InjectableFactory::class, $injectableFactory);
$obj = $injectableFactory->create(SomeClass::class);
$this->assertNotNull($obj);
$this->assertInstanceOf(
SomeImplementation::class,
$obj->get()
);
}
public function testService()
{
$bindingLoader = new class() implements BindingLoader
{
public function load(): BindingData
{
$data = new BindingData();
$binder = new Binder($data);
$binder->bindImplementation(
CacheItemPoolInterface::class,
FileCacheItemPool::class
);
$data->addGlobal(
SomeService::class,
Binding::createFromServiceName('someService')
);
return $data;
}
};
$someService = new SomeService();
$container = (new ContainerBuilder())
->withServices([
'someService' => $someService,
])
->withBindingLoader($bindingLoader)
->withParams(new ApplicationParams(noErrorHandler: true))
->build();
$injectableFactory = $container->get('injectableFactory');
$this->assertInstanceOf(InjectableFactory::class, $injectableFactory);
$obj = $injectableFactory->create(SomeClassRequiringService::class);
$this->assertNotNull($obj);
$this->assertSame(
$someService,
$obj->getService()
);
}
public function testValue()
{
$bindingLoader = new class() implements BindingLoader
{
public function load(): BindingData
{
$data = new BindingData();
$binder = new Binder($data);
$binder->bindImplementation(
CacheItemPoolInterface::class,
FileCacheItemPool::class
);
$data->addContext(
SomeClassRequiringValue::class,
'$value',
Binding::createFromValue('TEST_VALUE')
);
return $data;
}
};
$container = (new ContainerBuilder())
->withBindingLoader($bindingLoader)
->withParams(new ApplicationParams(noErrorHandler: true))
->build();
$injectableFactory = $container->get('injectableFactory');
$this->assertInstanceOf(InjectableFactory::class, $injectableFactory);
$obj = $injectableFactory->create(SomeClassRequiringValue::class);
$this->assertNotNull($obj);
$this->assertSame(
'TEST_VALUE',
$obj->getValue()
);
}
public function testQualifier(): void
{
$bindingLoader = new class() implements BindingLoader
{
public function load(): BindingData
{
$data = new BindingData();
$binder = new Binder($data);
$binder->bindImplementation(
CacheItemPoolInterface::class,
FileCacheItemPool::class
);
$binder->bindService(SomeService::class, 'testService');
$binder->bindService(QualifiedClassKey::create(SomeService::class, 'alt'), 'testServiceAlt');
return $data;
}
};
$testService = new SomeService();
$testServiceAlt = new SomeService('alt');
$container = (new ContainerBuilder())
->withBindingLoader($bindingLoader)
->withParams(new ApplicationParams(noErrorHandler: true))
->withServices([
'testService' => $testService,
'testServiceAlt' => $testServiceAlt,
])
->build();
$injectableFactory = $container->get('injectableFactory');
$this->assertInstanceOf(InjectableFactory::class, $injectableFactory);
$obj = $injectableFactory->create(SomeClassRequiringService::class);
$objAlt = $injectableFactory->create(SomeClassRequiringServiceAlt::class);
$this->assertInstanceOf(SomeService::class, $obj->getService());
$this->assertEquals('default', $obj->getService()->name);
$this->assertInstanceOf(SomeService::class, $objAlt->getService());
$this->assertEquals('alt', $objAlt->getService()->name);
}
}

View File

@@ -0,0 +1,42 @@
<?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\integration\Espo\Core;
use Espo\ORM\EntityManager;
class ContainerTest extends \tests\integration\Core\BaseTestCase
{
public function testGetByClass1(): void
{
$em = $this->getContainer()->getByClass(EntityManager::class);
$this->assertInstanceOf(EntityManager::class, $em);
}
}

View File

@@ -0,0 +1,385 @@
<?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 integration\Espo\Core\EntryPoint;
use Espo\Core\Api\ErrorOutput;
use Espo\Core\Api\Method;
use Espo\Core\Api\RequestWrapper;
use Espo\Core\Api\ResponseWrapper;
use Espo\Core\ApplicationState;
use Espo\Core\Binding\BindingContainerBuilder;
use Espo\Core\EntryPoint\OutputEmitter;
use Espo\Core\EntryPoint\RequestFactory;
use Espo\Core\EntryPoint\ResponseFactory;
use Espo\Core\EntryPoint\Starter;
use tests\integration\Core\BaseTestCase;
class EntryPointTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testNoAuth(): void
{
$this->reCreateApplication(reuse: true, noUser: true);
$outputEmitter = $this->createMock(OutputEmitter::class);
$errorOutput = $this->createMock(ErrorOutput::class);
$request = $this->createRequest(
method: Method::GET,
queryParams: [
'entryPoint' => 'oauthCallback',
],
);
$response = $this->createMock(ResponseWrapper::class);
$starter = $this->createStarter(
request: $request,
response: $response,
outputEmitter: $outputEmitter,
errorOutput: $errorOutput,
);
$errorOutput
->expects(self::never())
->method('processWithBodyPrinting');
$response
->expects(self::once())
->method('writeBody')
->with(
$this->callback(function (string $body) {
return str_contains($body, "Site URL");
})
);
$starter->start();
$applicationState = $this->getContainer()->getByClass(ApplicationState::class);
$this->assertTrue($applicationState->hasUser());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testNoPostMethod(): void
{
$this->reCreateApplication(reuse: true, noUser: true);
$outputEmitter = $this->createMock(OutputEmitter::class);
$errorOutput = $this->createMock(ErrorOutput::class);
$request = $this->createRequest(
method: Method::POST,
queryParams: [
'entryPoint' => 'oauthCallback',
],
);
$response = $this->createMock(ResponseWrapper::class);
$starter = $this->createStarter(
request: $request,
response: $response,
outputEmitter: $outputEmitter,
errorOutput: $errorOutput,
);
$errorOutput
->expects(self::once())
->method('processWithBodyPrinting');
$starter->start();
$applicationState = $this->getContainer()->getByClass(ApplicationState::class);
$this->assertFalse($applicationState->hasUser());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testNotExposed(): void
{
$metadata = $this->getMetadata();
$metadata->set('app', 'entryPoints', [
'oauthCallback' => [
'notExposed' => true,
],
]);
$metadata->save();
$this->reCreateApplication(reuse: true, noUser: true);
$outputEmitter = $this->createMock(OutputEmitter::class);
$errorOutput = $this->createMock(ErrorOutput::class);
$request = $this->createRequest(
method: Method::GET,
queryParams: [
'entryPoint' => 'oauthCallback',
],
);
$response = $this->createMock(ResponseWrapper::class);
$starter = $this->createStarter(
request: $request,
response: $response,
outputEmitter: $outputEmitter,
errorOutput: $errorOutput,
);
$errorOutput
->expects(self::once())
->method('processWithBodyPrinting');
$starter->start();
$applicationState = $this->getContainer()->getByClass(ApplicationState::class);
$this->assertFalse($applicationState->hasUser());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testAllowedMethods(): void
{
$metadata = $this->getMetadata();
$metadata->set('app', 'entryPoints', [
'oauthCallback' => [
'allowedMethods' => ['get', 'post'],
],
]);
$metadata->save();
$this->reCreateApplication(reuse: true, noUser: true);
$outputEmitter = $this->createMock(OutputEmitter::class);
$errorOutput = $this->createMock(ErrorOutput::class);
$request = $this->createRequest(
method: Method::POST,
queryParams: [
'entryPoint' => 'oauthCallback',
],
);
$response = $this->createMock(ResponseWrapper::class);
$starter = $this->createStarter(
request: $request,
response: $response,
outputEmitter: $outputEmitter,
errorOutput: $errorOutput,
);
$errorOutput
->expects(self::never())
->method('processWithBodyPrinting');
$starter->start();
$applicationState = $this->getContainer()->getByClass(ApplicationState::class);
$this->assertTrue($applicationState->hasUser());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testAuthRequired(): void
{
$this->reCreateApplication(reuse: true, noUser: true);
$outputEmitter = $this->createMock(OutputEmitter::class);
$errorOutput = $this->createMock(ErrorOutput::class);
$request = $this->createRequest(
method: Method::GET,
queryParams: [
'entryPoint' => 'avatar',
],
);
$response = $this->createMock(ResponseWrapper::class);
$starter = $this->createStarter(
request: $request,
response: $response,
outputEmitter: $outputEmitter,
errorOutput: $errorOutput,
);
$errorOutput
->expects(self::never())
->method('processWithBodyPrinting');
$response
->expects(self::never())
->method('writeBody');
$starter->start();
$applicationState = $this->getContainer()->getByClass(ApplicationState::class);
$this->assertFalse($applicationState->hasUser());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testConsumesFail(): void
{
$metadata = $this->getMetadata();
$metadata->set('app', 'entryPoints', [
'oauthCallback' => [
'consumes' => ['application/json'],
],
]);
$metadata->save();
$this->reCreateApplication(reuse: true, noUser: true);
$outputEmitter = $this->createMock(OutputEmitter::class);
$errorOutput = $this->createMock(ErrorOutput::class);
$request = $this->createRequest(
method: Method::GET,
queryParams: [
'entryPoint' => 'oauthCallback',
],
headers: [
'Content-Type' => 'text/plain',
],
);
$response = $this->createMock(ResponseWrapper::class);
$starter = $this->createStarter(
request: $request,
response: $response,
outputEmitter: $outputEmitter,
errorOutput: $errorOutput,
);
$errorOutput
->expects(self::once())
->method('processWithBodyPrinting');
$starter->start();
$applicationState = $this->getContainer()->getByClass(ApplicationState::class);
$this->assertFalse($applicationState->hasUser());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testConsumesSuccess(): void
{
$metadata = $this->getMetadata();
$metadata->set('app', 'entryPoints', [
'oauthCallback' => [
'consumes' => ['application/json'],
],
]);
$metadata->save();
$this->reCreateApplication(reuse: true, noUser: true);
$outputEmitter = $this->createMock(OutputEmitter::class);
$errorOutput = $this->createMock(ErrorOutput::class);
$request = $this->createRequest(
method: Method::GET,
queryParams: [
'entryPoint' => 'oauthCallback',
],
headers: [
'Content-Type' => 'application/json',
],
);
$response = $this->createMock(ResponseWrapper::class);
$starter = $this->createStarter(
request: $request,
response: $response,
outputEmitter: $outputEmitter,
errorOutput: $errorOutput,
);
$errorOutput
->expects(self::never())
->method('processWithBodyPrinting');
$starter->start();
$applicationState = $this->getContainer()->getByClass(ApplicationState::class);
$this->assertTrue($applicationState->hasUser());
}
private function createStarter(
RequestWrapper $request,
ResponseWrapper $response,
OutputEmitter $outputEmitter,
ErrorOutput $errorOutput,
): Starter {
$requestFactory = $this->createMock(RequestFactory::class);
$responseFactory = $this->createMock(ResponseFactory::class);
$requestFactory
->expects(self::once())
->method('create')
->willReturn($request);
$responseFactory
->expects(self::once())
->method('create')
->willReturn($response);
$binding = BindingContainerBuilder::create()
->bindInstance(RequestFactory::class, $requestFactory)
->bindInstance(ResponseFactory::class, $responseFactory)
->bindInstance(OutputEmitter::class, $outputEmitter)
->bindInstance(ErrorOutput::class, $errorOutput)
->build();
return $this->getInjectableFactory()->createWithBinding(Starter::class, $binding);
}
}

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\integration\Espo\Core\Field\Address;
use Espo\Core\Field\{
Address\AddressFormatterFactory,
Address,
};
class AddressFormatterTest extends \tests\integration\Core\BaseTestCase
{
public function testFormatter1()
{
$formatterFactory = $this->getContainer()->get('injectableFactory')->create(AddressFormatterFactory::class);
$formatter = $formatterFactory->create(1);
$address = Address::createBuilder()
->setStreet('street')
->setCity('city')
->setCountry('country')
->setState('state')
->setPostalCode('postalCode')
->build();
$expected =
"street\n" .
"city, state postalCode\n" .
"country";
$result = $formatter->format($address);
$this->assertEquals($expected, $result);
}
}

View File

@@ -0,0 +1,359 @@
<?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\integration\Espo\Core\Field;
use Espo\Core\Field\Address;
use Espo\Core\Field\Currency;
use Espo\Core\Field\Date;
use Espo\Core\Field\DateTime;
use Espo\Core\Field\DateTimeOptional;
use Espo\Core\Field\EmailAddress;
use Espo\Core\Field\EmailAddressGroup;
use Espo\Core\Field\Link;
use Espo\Core\Field\LinkMultiple;
use Espo\Core\Field\LinkMultipleItem;
use Espo\Core\Field\LinkParent;
use Espo\Core\Field\PhoneNumber;
use Espo\Core\Field\PhoneNumberGroup;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\ORM\EntityManager;
use tests\integration\Core\BaseTestCase;
class ValueObjectTest extends BaseTestCase
{
public function testAddress()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$entity = $entityManager->getNewEntity('Account');
$address = Address
::create()
->withCity('Test')
->withCountry('United States');
$entity->setBillingAddress($address);
$entityManager->saveEntity($entity);
$entity = $entityManager->getEntityById('Account', $entity->getId());
$address = $entity->getBillingAddress();
$this->assertNotNull($address);
$this->assertEquals('Test', $address->getCity());
$entity->setValueObject('billingAddress', null);
$address = $entity->getValueObject('billingAddress');
$this->assertNotNull($address);
$this->assertEquals(null, $address->getCity());
}
public function testCurrency()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$opportunity = $entityManager->getNewEntity('Opportunity');
$opportunity->set('name', 'opp-1');
$opportunity->setAmount(new Currency(1000.0, 'USD'));
$entityManager->saveEntity($opportunity);
$opportunity = $entityManager->getEntityById('Opportunity', $opportunity->getId());
$currency = $opportunity->getAmount();
$this->assertNotNull($currency);
$this->assertEquals(1000.0, $currency->getAmount());
$this->assertEquals('USD', $currency->getCode());
}
public function testDate()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$opportunity = $entityManager->getNewEntity('Opportunity');
$opportunity->set('name', 'opp-1');
$opportunity->setCloseDate(Date::fromString('2021-05-01'));
$entityManager->saveEntity($opportunity);
$opportunity = $entityManager->getEntityById('Opportunity', $opportunity->getId());
$closeDate = $opportunity->getCloseDate();
$this->assertNotNull($closeDate);
$this->assertEquals('2021-05-01', $closeDate->getString());
$opportunity->setValueObject('closeDate', null);
$entityManager->saveEntity($opportunity);
$opportunity = $entityManager->getEntityById('Opportunity', $opportunity->getId());
$closeDate = $opportunity->getValueObject('closeDate');
$this->assertNull($closeDate);
}
public function testDateTime()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$call = $entityManager->getNewEntity('Call');
$call->set('name', 'call-1');
$call->setValueObject('dateStart', DateTime::fromString('2021-05-01 10:00:00'));
$entityManager->saveEntity($call);
$call = $entityManager->getEntityById('Call', $call->getId());
$dateStart = $call->getValueObject('dateStart');
$this->assertNotNull($dateStart);
$this->assertEquals('2021-05-01 10:00:00', $dateStart->getString());
$call->setValueObject('dateStart', null);
$entityManager->saveEntity($call);
$call = $entityManager->getEntityById('Call', $call->getId());
$dateStart = $call->getValueObject('dateStart');
$this->assertNull($dateStart);
}
public function testDateTimeOptional()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$meeting = $entityManager->getRepositoryByClass(Meeting::class)->getNew();
$meeting->set('name', 'meeting-1');
$meeting->setDateStart(DateTimeOptional::fromString('2021-05-01 10:00:00'));
$entityManager->saveEntity($meeting);
$entityManager->refreshEntity($meeting);
$dateStart = $meeting->getDateStart();
$this->assertNotNull($dateStart);
$this->assertEquals('2021-05-01 10:00:00', $dateStart->getString());
$meeting->setValueObject('dateStart', null);
$entityManager->saveEntity($meeting);
$entityManager->refreshEntity($meeting);
$dateStart = $meeting->getValueObject('dateStart');
$this->assertNull($dateStart);
$meeting->setIsAllDay(true);
$meeting->setValueObject('dateStart', DateTimeOptional::fromString('2021-05-01'));
$entityManager->saveEntity($meeting);
$entityManager->refreshEntity($meeting);
$dateStart = $meeting->getValueObject('dateStart');
$this->assertNotNull($dateStart);
$this->assertEquals('2021-05-01', $dateStart->getString());
}
public function testEmailAddress()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$entity = $entityManager->getNewEntity('Account');
$group = EmailAddressGroup::create([
EmailAddress::create('one@test.com'),
EmailAddress::create('two@test.com')->optedOut(),
EmailAddress::create('three@test.com')->invalid(),
]);
$entity->setEmailAddressGroup($group);
$entityManager->saveEntity($entity);
$entity = $entityManager->getEntityById('Account', $entity->getId());
$group = $entity->getEmailAddressGroup();
$this->assertEquals('one@test.com', $group->getPrimary()->getAddress());
$this->assertTrue($group->getList()[1]->isOptedOut() || $group->getList()[2]->isOptedOut());
$this->assertTrue($group->getList()[1]->isInvalid() || $group->getList()[2]->isInvalid());
}
public function testPhoneNumber()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$entity = $entityManager->getNewEntity('Account');
$group = PhoneNumberGroup::create([
PhoneNumber::create('1')->withType('Office'),
PhoneNumber::create('2')->optedOut(),
PhoneNumber::create('3')->invalid(),
]);
$entity->setPhoneNumberGroup($group);
$entityManager->saveEntity($entity);
$entity = $entityManager->getEntityById('Account', $entity->getId());
$group = $entity->getPhoneNumberGroup();
$this->assertEquals('1', $group->getPrimary()->getNumber());
$this->assertEquals('Office', $group->getPrimary()->getType());
$this->assertTrue($group->getList()[1]->isOptedOut() || $group->getList()[2]->isOptedOut());
$this->assertTrue($group->getList()[1]->isInvalid() || $group->getList()[2]->isInvalid());
}
public function testLink()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$entity = $entityManager->getNewEntity('Account');
$entity->setValueObject('assignedUser', Link::create('1'));
$link = $entity->getValueObject('assignedUser');
$this->assertEquals('1', $link->getId());
$entity->setValueObject('assignedUser', null);
$this->assertNull($entity->getValueObject('assignedUser'));
}
public function testLinkParent()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$entity = $entityManager->getNewEntity('Task');
$entity->setValueObject('parent', LinkParent::create('Account', 'test-id'));
$link = $entity->getValueObject('parent');
$this->assertEquals('test-id', $link->getId());
$this->assertEquals('Account', $link->getEntityType());
$entity->setValueObject('parent', null);
$this->assertNull($entity->getValueObject('parent'));
}
public function testLinkMultiple1(): void
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$c1 = $entityManager->createEntity('Contact', []);
$c2 = $entityManager->createEntity('Contact', []);
$entity = $entityManager->getNewEntity('Opportunity');
$link = LinkMultiple::create([
LinkMultipleItem::create($c1->getId())->withColumnValue('role', 'Decision Maker'),
LinkMultipleItem::create($c2->getId()),
]);
$entity->setValueObject('contacts', $link);
$entityManager->saveEntity($entity);
$entity = $entityManager->getEntityById('Opportunity', $entity->getId());
/** @var LinkMultiple */
$link = $entity->getValueObject('contacts');
$this->assertEquals(2, $link->getCount());
$this->assertEquals('Decision Maker', $link->getById($c1->getId())->getColumnValue('role'));
$this->assertEquals(null, $link->getById($c2->getId())->getColumnValue('role'));
}
public function testLinkMultiple2()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$c1 = $entityManager->createEntity('Contact', []);
$c2 = $entityManager->createEntity('Contact', []);
$entity = $entityManager->getNewEntity('Opportunity');
$link = LinkMultiple::create([
LinkMultipleItem::create($c1->getId())->withColumnValue('role', 'Decision Maker'),
LinkMultipleItem::create($c2->getId()),
]);
$entity->setValueObject('contacts', $link);
$entityManager->saveEntity($entity);
$entity = $entityManager->getEntityById('Opportunity', $entity->getId());
$entity->loadLinkMultipleField('contacts');
/** @var LinkMultiple */
$link = $entity->getValueObject('contacts');
$this->assertEquals(2, $link->getCount());
$this->assertEquals('Decision Maker', $link->getById($c1->getId())->getColumnValue('role'));
}
}

View File

@@ -0,0 +1,161 @@
<?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\integration\Espo\Core\FieldProcessing;
use Espo\Core\ORM\EntityManager;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Core\Field\EmailAddress;
use Espo\Core\Field\EmailAddressGroup;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Record\UpdateParams;
use Espo\Modules\Crm\Entities\Contact;
use tests\integration\Core\BaseTestCase;
class EmailAddressTest extends BaseTestCase
{
public function testEmailAddress1(): void
{
/* @var $entityManager EntityManager */
$entityManager = $this->getContainer()->get('entityManager');
$contact = $entityManager->createEntity('Contact', [
'emailAddress' => 'test@test.com',
]);
$contact = $entityManager->getEntityById('Contact', $contact->getId());
/* @var $group1 EmailAddressGroup */
$group1 = $contact->getEmailAddressGroup();
$this->assertEquals(1, $group1->getCount());
$this->assertEquals('test@test.com', $group1->getPrimary()->getAddress());
$group2 = EmailAddressGroup::create()
->withAdded(
EmailAddress::create('test-a@test.com')->invalid()
)
->withAdded(
EmailAddress::create('test@test.com')->optedOut()
);
$contact->setValueObject('emailAddress', $group2);
$entityManager->saveEntity($contact);
$contact = $entityManager->getEntityById('Contact', $contact->getId());
/* @var $group3 EmailAddressGroup */
$group3 = $contact->getEmailAddressGroup();
$this->assertEquals(2, $group3->getCount());
$this->assertEquals('test-a@test.com', $group3->getPrimary()->getAddress());
$this->assertTrue($group3->getPrimary()->isInvalid());
$this->assertTrue($group3->getList()[1]->isOptedOut());
$group4 = EmailAddressGroup::create()
->withAdded(
EmailAddress::create('test-a@test.com')->invalid()
);
$contact->setValueObject('emailAddress', $group4);
$entityManager->saveEntity($contact);
$contact = $entityManager->getEntityById('Contact', $contact->getId());
/* @var $group5 EmailAddressGroup */
$group5 = $contact->getEmailAddressGroup();
$this->assertEquals(1, $group5->getCount());
}
public function testEmailAddress2(): void
{
/* @var $entityManager EntityManager */
$entityManager = $this->getContainer()->get('entityManager');
$lead = $entityManager->createEntity('Lead', [
'emailAddress' => 'test@test.com',
]);
$contact = $entityManager->createEntity('Contact', [
'emailAddress' => 'test@test.com',
]);
$contactFetched = $entityManager->getEntityById('Contact', $contact->getId());
/* @var $group EmailAddressGroup */
$group = $contactFetched->getEmailAddressGroup();
$this->assertEquals(1, $group->getCount());
$this->assertEquals('test@test.com', $group->getPrimary()->getAddress());
}
public function testPrimaryFirst(): void
{
$em = $this->getEntityManager();
$lead = $em->getRDBRepositoryByClass(Lead::class)
->getNew();
$lead->set('emailAddressData', [
(object) ['emailAddress' => 'test-1@test.com'],
(object) ['emailAddress' => 'test-2@test.com'],
(object) ['emailAddress' => 'test-3@test.com'],
(object) ['emailAddress' => 'test-4@test.com'],
]);
$em->saveEntity($lead);
$em->refreshEntity($lead);
$this->assertEquals('test-1@test.com', $lead->getEmailAddress());
}
public function testEmailAddress3(): void
{
$service = $this->getContainer()->getByClass(ServiceContainer::class)->getByClass(Contact::class);
$em = $this->getEntityManager();
/** @var Contact $contact */
$contact = $em->createEntity(Contact::ENTITY_TYPE);
/** @noinspection PhpUnhandledExceptionInspection */
$service->update($contact->getId(), (object) [
'emailAddress' => 'test@test.com',
], UpdateParams::create());
$em->refreshEntity($contact);
$this->assertEquals('test@test.com', $contact->getEmailAddress());
}
}

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\integration\Espo\Core\FieldProcessing;
use Espo\Core\{
ORM\EntityManager,
};
class FileTest extends \tests\integration\Core\BaseTestCase
{
public function testFile1(): void
{
/* @var $entityManager EntityManager */
$entityManager = $this->getContainer()->get('entityManager');
$attachment1 = $entityManager->createEntity('Attachment', [
'contents' => 'test-1',
'relatedType' => 'Document',
]);
$document = $entityManager->createEntity('Document', [
'fileId' => $attachment1->getId(),
]);
$attachment1 = $entityManager->getEntityById('Attachment', $attachment1->getId());
$this->assertEquals($document->getId(), $attachment1->get('relatedId'));
$attachment2 = $entityManager->createEntity('Attachment', [
'contents' => 'test-2',
'relatedType' => 'Document',
]);
$document->set('fileId', $attachment2->getId());
$entityManager->saveEntity($document);
$attachment2 = $entityManager->getEntityById('Attachment', $attachment2->getId());
$this->assertEquals($document->getId(), $attachment2->get('relatedId'));
$attachment1 = $entityManager->getEntityById('Attachment', $attachment1->getId());
$this->assertNull($attachment1);
}
}

View File

@@ -0,0 +1,141 @@
<?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\integration\Espo\Core\FieldProcessing;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Field\PhoneNumber;
use Espo\Core\Field\PhoneNumberGroup;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Record\UpdateParams;
use Espo\Modules\Crm\Entities\Contact;
use tests\integration\Core\BaseTestCase;
class PhoneNumberTest extends BaseTestCase
{
public function testPhoneNumber1(): void
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$contact = $entityManager->getNewEntity(Contact::ENTITY_TYPE);
$contact->set('phoneNumber', '+1');
$entityManager->saveEntity($contact);
$this->assertEquals('+1', $contact->get('phoneNumber'));
$contact = $entityManager->getEntityById('Contact', $contact->getId());
/* @var $group1 PhoneNumberGroup */
$group1 = $contact->getPhoneNumberGroup();
$this->assertEquals(1, $group1->getCount());
$this->assertEquals('+1', $group1->getPrimary()->getNumber());
$group2 = PhoneNumberGroup
::create()
->withAdded(
PhoneNumber::create('+2')->invalid()
)
->withAdded(
PhoneNumber::create('+1')->optedOut()
);
$contact->setValueObject('phoneNumber', $group2);
$entityManager->saveEntity($contact);
$contact = $entityManager->getEntityById('Contact', $contact->getId());
/* @var $group3 PhoneNumberGroup */
$group3 = $contact->getPhoneNumberGroup();
$this->assertEquals(2, $group3->getCount());
$this->assertEquals('+2', $group3->getPrimary()->getNumber());
$this->assertTrue($group3->getPrimary()->isInvalid());
$this->assertTrue($group3->getList()[1]->isOptedOut());
$group4 = PhoneNumberGroup
::create()
->withAdded(
PhoneNumber::create('+2')->invalid()
);
$contact->setValueObject('phoneNumber', $group4);
$entityManager->saveEntity($contact);
$contact = $entityManager->getEntityById('Contact', $contact->getId());
/* @var $group5 PhoneNumberGroup */
$group5 = $contact->getPhoneNumberGroup();
$this->assertEquals(1, $group5->getCount());
}
public function testPrimaryFirst(): void
{
$em = $this->getEntityManager();
$lead = $em->getRDBRepositoryByClass(Lead::class)
->getNew();
$lead->set('phoneNumberData', [
(object) ['phoneNumber' => '+0000000001'],
(object) ['phoneNumber' => '+0000000002'],
(object) ['phoneNumber' => '+0000000003'],
(object) ['phoneNumber' => '+0000000004'],
]);
$em->saveEntity($lead);
$em->refreshEntity($lead);
$this->assertEquals('+0000000001', $lead->getPhoneNumber());
}
public function testPhoneNumber2(): void
{
$service = $this->getContainer()->getByClass(ServiceContainer::class)->getByClass(Contact::class);
$em = $this->getEntityManager();
/** @var Contact $contact */
$contact = $em->createEntity(Contact::ENTITY_TYPE);
/** @noinspection PhpUnhandledExceptionInspection */
$service->update($contact->getId(), (object) [
'phoneNumber' => '+11111111111',
], UpdateParams::create());
$em->refreshEntity($contact);
$this->assertEquals('+11111111111', $contact->getPhoneNumber());
}
}

View File

@@ -0,0 +1,228 @@
<?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\integration\Espo\Core\FieldProcessing;
use Espo\Core\ORM\EntityManager;
use Espo\Modules\Crm\Entities\Contact;
use tests\integration\Core\BaseTestCase;
use Espo\Modules\Crm\Entities\Opportunity;
class RelationTest extends BaseTestCase
{
public function testLinkMultiple1(): void
{
/* @var $entityManager EntityManager */
$entityManager = $this->getContainer()->get('entityManager');
$contact1 = $entityManager->createEntity('Contact', []);
$contact2 = $entityManager->createEntity('Contact', []);
$contact3 = $entityManager->createEntity('Contact', []);
$opportunity = $entityManager->createEntity('Opportunity', [
'contactsIds' => [
$contact1->getId(),
$contact2->getId(),
],
'contactsColumns' => (object) [
$contact1->getId() => (object) [
'role' => 'Decision Maker',
],
],
]);
$opportunity = $entityManager->getEntityById('Opportunity', $opportunity->getId());
$this->assertEquals(
self::sortArray([
$contact1->getId(),
$contact2->getId(),
]),
self::sortArray($opportunity->getLinkMultipleIdList('contacts'))
);
$column1 = $entityManager
->getRDBRepository('Opportunity')
->getRelation($opportunity, 'contacts')
->getColumn($contact1, 'role');
$this->assertEquals('Decision Maker', $column1);
$opportunity->set([
'contactsIds' => [
$contact2->getId(),
$contact3->getId(),
],
'contactsColumns' => (object) [
$contact2->getId() => (object) [
'role' => null,
],
$contact3->getId() => (object) [
'role' => 'Evaluator',
],
],
]);
$entityManager->saveEntity($opportunity);
$opportunity = $entityManager->getEntityById('Opportunity', $opportunity->getId());
$this->assertEquals(
self::sortArray([
$contact2->getId(),
$contact3->getId(),
]),
self::sortArray($opportunity->getLinkMultipleIdList('contacts'))
);
$column3 = $entityManager
->getRDBRepository('Opportunity')
->getRelation($opportunity, 'contacts')
->getColumn($contact3, 'role');
$this->assertEquals('Evaluator', $column3);
$opportunity = $entityManager->getEntityById('Opportunity', $opportunity->getId());
$opportunity->set([
'contactsColumns' => (object) [
$contact3->getId() => (object) [
'role' => 'Decision Maker',
],
],
]);
$entityManager->saveEntity($opportunity);
$column3 = $entityManager
->getRDBRepository('Opportunity')
->getRelation($opportunity, 'contacts')
->getColumn($contact3, 'role');
$this->assertEquals('Decision Maker', $column3);
// W/o link-multiple.
$opp1 = $entityManager->getRDBRepositoryByClass(Opportunity::class)->getNew();
$entityManager->saveEntity($opp1);
$opp2 = $entityManager->getRDBRepositoryByClass(Opportunity::class)->getNew();
$entityManager->saveEntity($opp2);
$contact = $entityManager->getRDBRepositoryByClass(Contact::class)->getNew();
$contact->setMultiple([
'opportunitiesIds' => [$opp1->getId()]
]);
$entityManager->saveEntity($contact);
$this->assertTrue(
$entityManager->getRelation($contact, 'opportunities')->isRelatedById($opp1->getId())
);
// Do not allow update IDs if no link-multiple field.
$contact->setMultiple([
'opportunitiesIds' => [$opp2->getId()]
]);
$entityManager->saveEntity($contact);
$this->assertTrue(
$entityManager->getRelation($contact, 'opportunities')->isRelatedById($opp1->getId())
);
$this->assertFalse(
$entityManager->getRelation($contact, 'opportunities')->isRelatedById($opp2->getId())
);
}
public function testHasOne(): void
{
/* @var $entityManager EntityManager */
$entityManager = $this->getContainer()->get('entityManager');
$lead1 = $entityManager->createEntity('Lead', []);
$lead2 = $entityManager->createEntity('Lead', []);
$account = $entityManager->createEntity('Account', []);
$lead1->set('createdAccountId', $account->getId());
$entityManager->saveEntity($lead1);
$lead2->set('createdAccountId', $account->getId());
$entityManager->saveEntity($lead2);
$lead1 = $entityManager->getEntityById('Lead', $lead1->getId());
$this->assertNull($lead1->get('createdAccountId'));
$lead2 = $entityManager->getEntityById('Lead', $lead2->getId());
$this->assertEquals($account->getId(), $lead2->get('createdAccountId'));
}
public function testBelongsToHasOne(): void
{
/* @var $entityManager EntityManager */
$entityManager = $this->getContainer()->get('entityManager');
$lead1 = $entityManager->createEntity('Lead', []);
$lead2 = $entityManager->createEntity('Lead', []);
$account = $entityManager->createEntity('Account', [
'originalLeadId' => $lead1->getId(),
]);
$entityManager->saveEntity($account);
$account = $entityManager->getEntityById('Account', $account->getId());
$account->set([
'originalLeadId' => $lead2->getId(),
]);
$entityManager->saveEntity($account);
$lead2 = $entityManager->getEntityById('Lead', $lead2->getId());
$this->assertEquals($account->getId(), $lead2->get('createdAccountId'));
$lead1 = $entityManager->getEntityById('Lead', $lead1->getId());
$this->assertEquals(null, $lead1->get('createdAccountId'));
}
private static function sortArray(array $array): array
{
sort($array);
return $array;
}
}

View File

@@ -0,0 +1,139 @@
<?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\integration\Espo\Core\FieldProcessing;
use DateTimeImmutable;
use Espo\Core\Authentication\Util\DelayUtil;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingProcessor;
use Espo\Core\Field\DateTime;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\DateTime\Clock;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\Modules\Crm\Entities\Reminder;
use tests\integration\Core\BaseTestCase;
class ReminderTest extends BaseTestCase
{
public function testOne(): void
{
/* @var $entityManager EntityManager */
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$user = $this->getContainer()->getByClass(User::class);
$meeting = $entityManager->createEntity('Meeting', [
'dateStart' => DateTime::createNow()->modify('+1 day')->toString(),
'usersIds' => [$user->getId()],
'reminders' => [
(object) [
'type' => 'Popup',
'seconds' => 0,
],
(object) [
'type' => 'Popup',
'seconds' => 60,
]
]
]);
$reminderList = $entityManager
->getRDBRepository(Reminder::ENTITY_TYPE)
->where([
'entityId' => $meeting->getId(),
'entityType' => $meeting->getEntityType(),
])
->order('remindAt')
->find();
$this->assertEquals(2, count($reminderList));
}
public function testFallsInPast(): void
{
$clock = $this->createMock(Clock::class);
$clock->method('now')
->willReturn(new DateTimeImmutable('2030-01-01 00:00'));
$app = $this->createApplication(
binding: new class ($clock) implements BindingProcessor {
public function __construct(private Clock $clock) {}
public function process(Binder $binder): void
{
$binder->bindInstance(Clock::class, $this->clock);
}
},
// @todo Need to reset loaded hooks in the HookManager?
//reuse: true,
);
$this->setApplication($app);
$entityManager = $this->getEntityManager();
$user = $this->getContainer()->getByClass(User::class);
$meeting = $entityManager->createEntity(Meeting::ENTITY_TYPE, [
'dateStart' => DateTime::fromDateTime($clock->now())->modify('+10 minutes')->toString(),
'usersIds' => [$user->getId()],
'reminders' => [
(object) [
'type' => Reminder::TYPE_POPUP,
'seconds' => 60 * 60,
],
(object) [
'type' => Reminder::TYPE_POPUP,
'seconds' => 120 * 60,
],
(object) [
'type' => Reminder::TYPE_EMAIL,
'seconds' => 60 * 60,
],
(object) [
'type' => Reminder::TYPE_EMAIL,
'seconds' => 120 * 60,
],
]
]);
$reminderList = $entityManager
->getRDBRepositoryByClass(Reminder::class)
->where([
'entityId' => $meeting->getId(),
'entityType' => $meeting->getEntityType(),
])
->find();
$this->assertCount(2, $reminderList);
}
}

File diff suppressed because it is too large Load Diff

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\integration\Espo\Core\FulltextIndex;
use Espo\Core\Utils\Util;
use PHPUnit\Framework\Attributes\DataProvider;
use tests\integration\Core\BaseTestCase;
class CheckCreatedIndexTest extends BaseTestCase
{
protected ?string $dataFile = 'InitData.php';
protected ?string $pathToFiles = 'Core/FulltextIndex/customFiles';
static public function entityList()
{
return [
['Email'],
['Account'],
['Contact'],
];
}
#[DataProvider('entityList')]
public function testCreatedIndexes($entityName)
{
$entityManager = $this->getContainer()->get('entityManager');
$pdo = $entityManager->getPDO();
$fulltextFieldList = $entityManager->getMetadata()->get($entityName, 'fullTextSearchColumnList');
if (!$fulltextFieldList) {
$this->assertNull($fulltextFieldList);
return;
}
$query = "SHOW INDEX FROM `". Util::toCamelCase($entityName) ."` WHERE Index_type = 'FULLTEXT'";
$sth = $pdo->prepare($query);
$sth->execute();
$rowList = $sth->fetchAll(\PDO::FETCH_ASSOC);
$this->assertNotEmpty($rowList);
$result = [];
foreach ($rowList as $row) {
$result[] = Util::toCamelCase($row['Column_name']);
}
asort($fulltextFieldList);
asort($result);
$this->assertEquals($fulltextFieldList, $result);
}
}

View File

@@ -0,0 +1,219 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Core\Job;
use Espo\Core\InjectableFactory;
use Espo\Core\Job\Job\Status;
use Espo\Core\Job\JobManager;
use Espo\Core\Job\JobSchedulerFactory;
use Espo\Core\Job\PrepareProcessor;
use Espo\Core\Job\QueueName;
use Espo\Entities\Job as JobEntity;
use Espo\ORM\EntityManager;
use tests\integration\Core\BaseTestCase;
use tests\integration\testClasses\Job\Job as TestJob;
class JobTest extends BaseTestCase
{
/**
* @var JobManager
*/
private $jobManager;
/** @var EntityManager */
private $entityManager;
/**
* @var JobSchedulerFactory
*/
private $schedulerFactory;
private ?PrepareProcessor $prepareProcessor = null;
protected function setUp(): void
{
parent::setUp();
$this->jobManager = $this->getContainer()->getByClass(JobManager::class);
$this->prepareProcessor = $this->getInjectableFactory()->create(PrepareProcessor::class);
$this->entityManager = $this->getContainer()->getByClass(EntityManager::class);
$this->schedulerFactory = $this->getContainer()
->getByClass(InjectableFactory::class)
->create(JobSchedulerFactory::class);
}
public function testScheduler(): void
{
$this->schedulerFactory
->create()
->setClassName(TestJob::class)
->setQueue(QueueName::Q0)
->schedule();
$this->jobManager->processQueue(QueueName::Q0, 10);
$job = $this->entityManager
->getRDBRepositoryByClass(JobEntity::class)
->where(['className' => TestJob::class])
->findOne();
$this->assertNotNull($job);
$this->assertEquals(Status::SUCCESS, $job->getStatus());
}
public function testProcessQueueNoGroup(): void
{
$job = $this->entityManager->createEntity('Job', [
'job' => 'Dummy',
'queue' => 'q0',
]);
$this->jobManager->processQueue('q0', 10);
$jobReloaded = $this->entityManager->getEntityById('Job', $job->getId());
$this->assertEquals(Status::SUCCESS, $jobReloaded->getStatus());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testProcessQueueGroupAll(): void
{
$job1 = $this->entityManager->createEntity('Job', [
'job' => 'Dummy',
'group' => 'group-0',
]);
$job2 = $this->entityManager->createEntity('Job', [
'job' => 'Dummy',
'group' => 'group-1',
]);
$job3 = $this->entityManager->createEntity('Job', [
'job' => 'Dummy',
'group' => 'group-1',
]);
$this->prepareProcessor->process();
$this->jobManager->processMainQueue();
$job1Reloaded = $this->entityManager->getEntityById('Job', $job1->getId());
$job2Reloaded = $this->entityManager->getEntityById('Job', $job2->getId());
$job3Reloaded = $this->entityManager->getEntityById('Job', $job3->getId());
$this->assertEquals(Status::SUCCESS, $job1Reloaded->getStatus());
$this->assertEquals(Status::SUCCESS, $job2Reloaded->getStatus());
$this->assertEquals(Status::SUCCESS, $job3Reloaded->getStatus());
}
public function testProcessQueueGroupSeparate(): void
{
$job1 = $this->entityManager->createEntity('Job', [
'job' => 'Dummy',
'group' => 'group-0',
]);
$job2 = $this->entityManager->createEntity('Job', [
'job' => 'Dummy',
'group' => 'group-1',
]);
$job3 = $this->entityManager->createEntity('Job', [
'job' => 'Dummy',
'group' => 'group-1',
]);
$this->jobManager->processGroup('group-1', 100);
$job1Reloaded = $this->entityManager->getEntityById('Job', $job1->getId());
$job2Reloaded = $this->entityManager->getEntityById('Job', $job2->getId());
$job3Reloaded = $this->entityManager->getEntityById('Job', $job3->getId());
$this->assertEquals(Status::PENDING, $job1Reloaded->getStatus());
$this->assertEquals(Status::SUCCESS, $job2Reloaded->getStatus());
$this->assertEquals(Status::SUCCESS, $job3Reloaded->getStatus());
$this->jobManager->processGroup('group-0', 100);
$job1Reloaded2 = $this->entityManager->getEntityById('Job', $job1->getId());
$this->assertEquals(Status::SUCCESS, $job1Reloaded2->getStatus());
}
public function testRunJobById(): void
{
$job = $this->entityManager->createEntity('Job', [
'job' => 'Dummy',
'status' => Status::READY,
]);
$this->jobManager->runJobById($job->getId());
$jobReloaded = $this->entityManager->getEntityById('Job', $job->getId());
$this->assertEquals(Status::SUCCESS, $jobReloaded->getStatus());
}
public function testRunJobByEntity(): void
{
$job = $this->entityManager->createEntity('Job', [
'job' => 'Dummy',
]);
$this->jobManager->runJob($job);
$jobReloaded = $this->entityManager->getEntityById('Job', $job->getId());
$this->assertEquals(Status::SUCCESS, $jobReloaded->getStatus());
}
public function testRunJobWithClassName(): void
{
$job = $this->entityManager->createEntity('Job', [
'className' => TestJob::class,
'data' => (object) [
'test' => '1',
],
]);
$this->jobManager->runJob($job);
$jobReloaded = $this->entityManager->getEntityById('Job', $job->getId());
$this->assertEquals(Status::SUCCESS, $jobReloaded->getStatus());
}
}

View File

@@ -0,0 +1,221 @@
<?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\integration\Espo\Core\Mail;
use Espo\Core\Mail\Importer\DefaultParentFinder;
use Espo\Core\Mail\Message;
use Espo\Entities\Email;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Lead;
use tests\integration\Core\BaseTestCase;
class ParentFinderTest extends BaseTestCase
{
public function testReplied(): void
{
$em = $this->getEntityManager();
$account = $em->createEntity(Account::ENTITY_TYPE, [
'name' => 'Test'
]);
$emailOne = $em->createEntity(Email::ENTITY_TYPE, [
'parentId' => $account->getId(),
'parentType' => $account->getEntityType(),
'status' => Email::STATUS_ARCHIVED,
]);
/** @var Email $email */
$email = $em->getNewEntity(Email::ENTITY_TYPE);
$email->set([
'repliedId' => $emailOne->getId(),
]);
$finder = $this->getInjectableFactory()->create(DefaultParentFinder::class);
$message = $this->createMock(Message::class);
$parent = $finder->find($email, $message);
$this->assertInstanceOf(Account::class, $parent);
$this->assertEquals($account->getId(), $parent->getId());
}
public function testReferences(): void
{
$em = $this->getEntityManager();
$account = $em->createEntity(Account::ENTITY_TYPE, [
'name' => 'Test'
]);
/** @var Email $email */
$email = $em->getNewEntity(Email::ENTITY_TYPE);
$finder = $this->getInjectableFactory()->create(DefaultParentFinder::class);
$message = $this->createMock(Message::class);
$message
->expects($this->once())
->method('getHeader')
->with('References')
->willReturn('Account/' . $account->getId(). '/1667915635/6605@espo');
$parent = $finder->find($email, $message);
$this->assertInstanceOf(Account::class, $parent);
$this->assertEquals($account->getId(), $parent->getId());
}
public function testFromAddressAccount(): void
{
$em = $this->getEntityManager();
/** @var Account $subject */
$subject = $em->createEntity(Account::ENTITY_TYPE, [
'emailAddress' => 'subject@test.com',
]);
/** @var Email $email */
$email = $em->getNewEntity(Email::ENTITY_TYPE);
$email->setFromAddress($subject->getEmailAddress());
$finder = $this->getInjectableFactory()->create(DefaultParentFinder::class);
$message = $this->createMock(Message::class);
$parent = $finder->find($email, $message);
$this->assertInstanceOf(Account::class, $parent);
$this->assertEquals($subject->getId(), $parent->getId());
}
public function testFromAddressContact(): void
{
$em = $this->getEntityManager();
/** @var Contact $subject */
$subject = $em->createEntity(Contact::ENTITY_TYPE, [
'emailAddress' => 'subject@test.com',
]);
/** @var Email $email */
$email = $em->getNewEntity(Email::ENTITY_TYPE);
$email->setFromAddress($subject->getEmailAddress());
$finder = $this->getInjectableFactory()->create(DefaultParentFinder::class);
$message = $this->createMock(Message::class);
$parent = $finder->find($email, $message);
$this->assertInstanceOf(Contact::class, $parent);
$this->assertEquals($subject->getId(), $parent->getId());
}
public function testFromAddressLead(): void
{
$em = $this->getEntityManager();
/** @var Lead $subject */
$subject = $em->createEntity(Lead::ENTITY_TYPE, [
'emailAddress' => 'subject@test.com',
]);
/** @var Email $email */
$email = $em->getNewEntity(Email::ENTITY_TYPE);
$email->setFromAddress($subject->getEmailAddress());
$finder = $this->getInjectableFactory()->create(DefaultParentFinder::class);
$message = $this->createMock(Message::class);
$parent = $finder->find($email, $message);
$this->assertInstanceOf(Lead::class, $parent);
$this->assertEquals($subject->getId(), $parent->getId());
}
public function testReplyToAddress(): void
{
$em = $this->getEntityManager();
/** @var Lead $subject */
$subject = $em->createEntity(Lead::ENTITY_TYPE, [
'emailAddress' => 'subject@test.com',
]);
/** @var Email $email */
$email = $em->getNewEntity(Email::ENTITY_TYPE);
$email->setFromAddress('any@address.com');
$email->addReplyToAddress($subject->getEmailAddress());
$finder = $this->getInjectableFactory()->create(DefaultParentFinder::class);
$message = $this->createMock(Message::class);
$parent = $finder->find($email, $message);
$this->assertInstanceOf(Lead::class, $parent);
$this->assertEquals($subject->getId(), $parent->getId());
}
public function testToAddress(): void
{
$em = $this->getEntityManager();
/** @var Lead $subject */
$subject = $em->createEntity(Lead::ENTITY_TYPE, [
'emailAddress' => 'subject@test.com',
]);
/** @var Email $email */
$email = $em->getNewEntity(Email::ENTITY_TYPE);
$email->setFromAddress('any@address.com');
$email->addToAddress($subject->getEmailAddress());
$finder = $this->getInjectableFactory()->create(DefaultParentFinder::class);
$message = $this->createMock(Message::class);
$parent = $finder->find($email, $message);
$this->assertInstanceOf(Lead::class, $parent);
$this->assertEquals($subject->getId(), $parent->getId());
}
}

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\integration\Espo\Core\Notification;
use Espo\ORM\EntityManager;
use Espo\Core\Utils\Config\ConfigWriter;
use Espo\Core\InjectableFactory;
use Espo\Entities\User;
use tests\integration\Core\BaseTestCase;
class AssignmentNotificatorTest extends BaseTestCase
{
/**
* @var EntityManager
*/
private $entityManager;
/**
* @var User
*/
private $user1;
/**
* @var User
*/
private $user2;
public function setUp(): void
{
parent::setUp();
$this->initTestData();
$this->entityManager = $this->getEntityManager();
}
private function initTestData() : void
{
$configWriter = $this->getContainer()->getByClass(InjectableFactory::class)->create(ConfigWriter::class);
$this->getMetadata()->set('scopes', 'Meeting', [
'stream' => false,
]);
$this->getMetadata()->save();
$configWriter->set('assignmentNotificationsEntityList', [
'Email',
'Meeting',
]);
$configWriter->save();
$em = $this->getEntityManager();
$role = $em->createEntity('Role', [
'name' => 'test',
'data' => [
'Meeting' => [
'read' => 'own',
],
'Email' => [
'read' => 'own',
],
],
]);
$this->user1 = $em->createEntity('User', [
'userName' => 'test-1',
'lastName' => 'Test 1',
'rolesIds' => [$role->getId()],
'emailAddress' => 'test1@test.com',
]);
$this->user2 = $em->createEntity('User', [
'userName' => 'test-2',
'lastName' => 'Test 2',
'rolesIds' => [$role->getId()],
'emailAddress' => 'test2@test.com',
]);
$em->createEntity('User', [
'userName' => 'test-3',
'lastName' => 'Test 3',
'rolesIds' => [],
'emailAddress' => 'test3@test.com',
]);
$preferences2 = $em->getEntityById('Preferences', $this->user2->getId());
$preferences2->set([
'assignmentNotificationsIgnoreEntityTypeList' => ['Meeting'],
]);
$em->saveEntity($preferences2);
}
public function testAssignmentSelf() : void
{
$meeting = $this->entityManager->createEntity('Meeting', [
'name' => 'test',
'assignedUserId' => $this->user1->getId(),
'createdById' => $this->user1->getId(),
]);
$notification = $this->entityManager
->getRDBRepository('Notification')
->where([
'userId' => '1',
'type' => 'Assign',
'relatedType' => 'Meeting',
'relatedId' => $meeting->getId(),
])
->findOne();
$this->assertNull($notification, 'notification');
}
public function testAssignmentEnabled() : void
{
$meeting = $this->entityManager->createEntity('Meeting', [
'name' => 'test',
'assignedUserId' => $this->user1->getId(),
]);
$notification = $this->entityManager
->getRDBRepository('Notification')
->where([
'userId' => $this->user1->getId(),
'type' => 'Assign',
'relatedType' => 'Meeting',
'relatedId' => $meeting->getId(),
])
->findOne();
$this->assertNotNull($notification);
}
public function testAssignmentDisabled() : void
{
$call = $this->entityManager->createEntity('Call', [
'name' => 'test',
'assignedUserId' => $this->user1->getId(),
]);
$notification = $this->entityManager
->getRDBRepository('Notification')
->where([
'userId' => $this->user1->getId(),
'type' => 'Assign',
'relatedType' => 'Call',
'relatedId' => $call->getId(),
])
->findOne();
$this->assertNull($notification);
}
public function testAssignmentIgnored() : void
{
$meeting = $this->entityManager->createEntity('Meeting', [
'name' => 'test',
'assignedUserId' => $this->user2->getId(),
]);
$notification = $this->entityManager
->getRDBRepository('Notification')
->where([
'userId' => $this->user1->getId(),
'type' => 'Assign',
'relatedType' => 'Meeting',
'relatedId' => $meeting->getId(),
])
->findOne();
$this->assertNull($notification, 'notification');
}
public function testAssignmentEmailYes() : void
{
$email = $this->entityManager->createEntity('Email', [
'name' => 'test',
'status' => 'Archived',
'from' => $this->user2->get('emailAddress'),
'to' => $this->user1->get('emailAddress'),
'dateSent' => date('Y-m-d H:i:s'),
]);
$notification = $this->entityManager
->getRDBRepository('Notification')
->where([
'userId' => $this->user1->getId(),
'type' => 'EmailReceived',
'relatedType' => 'Email',
'relatedId' => $email->getId(),
])
->findOne();
$this->assertNotNull($notification);
}
}

View File

@@ -0,0 +1,77 @@
<?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\integration\Espo\Core\ORM;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Opportunity;
use tests\integration\Core\BaseTestCase;
class CoreEntityTest extends BaseTestCase
{
public function testLinkMultiple(): void
{
$contact1 = $this->getEntityManager()->createEntity(Contact::ENTITY_TYPE, [
'lastName' => 'Test 1',
]);
$contact2 = $this->getEntityManager()->createEntity(Contact::ENTITY_TYPE, [
'lastName' => 'Test 2',
]);
$opp = $this->getEntityManager()->createEntity(Opportunity::ENTITY_TYPE, [
'name' => 'Test',
'contactsIds' => [$contact1->getId()],
'contactsColumns' => [
$contact1->getId() => ['role' => 'Evaluator']
],
]);
$opp = $this->getEntityManager()
->getRDBRepositoryByClass(Opportunity::class)
->getById($opp->getId());
$this->assertTrue(in_array($contact1->getId(), $opp->getLinkMultipleIdList('contacts')));
$this->assertEquals('Evaluator', $opp->getLinkMultipleColumn('contacts', 'role', $contact1->getId()));
$opp->addLinkMultipleId('contacts', $contact2->getId());
$opp->setLinkMultipleColumn('contacts', 'role', $contact2->getId(), 'Decision Maker');
$this->assertTrue($opp->hasLinkMultipleId('contacts', $contact2->getId()));
$this->assertEquals('Decision Maker', $opp->getLinkMultipleColumn('contacts', 'role', $contact2->getId()));
// Rewrites set values.
$opp->loadLinkMultipleField('contacts');
$this->assertFalse($opp->hasLinkMultipleId('contacts', $contact2->getId()));
$this->assertTrue(in_array($contact1->getId(), $opp->getFetched('contactsIds')));
$this->assertFalse(in_array($contact2->getId(), $opp->getFetched('contactsIds')));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
<?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\integration\Espo\Core\Utils;
class ClassFinderTest extends \tests\integration\Core\BaseTestCase
{
public function testFind1()
{
$classFinder = $this->getContainer()->get('classFinder');
$this->assertEquals(
'Espo\\Modules\\Crm\\Entities\\Account',
$classFinder->find('Entities', 'Account')
);
$this->assertEquals(
'Espo\\Entities\\Email',
$classFinder->find('Entities', 'Email')
);
$this->assertTrue(file_exists('data/cache/application/classmapEntities.php'));
}
}

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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
use PHPUnit\Framework\Attributes\DataProvider;
#[NoTransaction]
class AddressFieldTest extends Base
{
static public function fieldList()
{
return [
['testAddressStreet', 255],
['testAddressCity', 100],
['testAddressState', 100],
['testAddressCountry', 100],
['testAddressPostalCode', 40],
];
}
#[DataProvider('fieldList')]
public function testColumn($fieldName, $length)
{
$column = $this->getColumnInfo('Test', $fieldName);
$this->assertNotEmpty($column);
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals($length, $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
}

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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
#[NoTransaction]
class ArrayFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Test', 'testArray');
$this->assertNotEmpty($column);
$this->assertEquals('mediumtext', $column['COLUMN_TYPE']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
}

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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
#[NoTransaction]
class AutoIncrementFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Case', 'number');
$this->assertNotEmpty($column);
$this->assertEquals('int', $column['DATA_TYPE']);
$this->assertEquals('NO', $column['IS_NULLABLE']);
$this->assertEquals('10', $column['NUMERIC_PRECISION']);
$this->assertEquals('auto_increment', $column['EXTRA']);
$this->assertEquals('UNI', $column['COLUMN_KEY']);
}
public function testColumnOnExistingTable(): void
{
$this->updateDefs('Test', 'testAutoIncrement', [
'type' => 'autoincrement',
]);
$column = $this->getColumnInfo('Test', 'testAutoIncrement');
$this->assertNotEmpty($column);
$this->assertEquals('int', $column['DATA_TYPE']);
$this->assertEquals('NO', $column['IS_NULLABLE']);
$this->assertEquals('10', $column['NUMERIC_PRECISION']);
$this->assertEquals('auto_increment', $column['EXTRA']);
$this->assertEquals('UNI', $column['COLUMN_KEY']);
}
public function testDeleteColumnOnExistingTable(): void
{
// 1. Create "testAutoIncrement" field
$this->testColumnOnExistingTable();
// 2. Delete "testAutoIncrement" field
$this->getMetadata()->delete('entityDefs', 'Test', ['fields.testAutoIncrement']);
$this->getMetadata()->save();
// Issue that it requires rebuilding between removing and adding a new indexes.
$this->getDataManager()->rebuild();
$column = $this->getColumnInfo('Test', 'testAutoIncrement');
$this->assertNotEmpty($column);
$this->assertEquals('int', $column['DATA_TYPE']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('10', $column['NUMERIC_PRECISION']);
$this->assertEmpty($column['EXTRA']);
$this->assertEmpty($column['COLUMN_KEY']);
}
public function testDeleteCreateColumnOnExistingTable(): void
{
// 1. Create "testAutoIncrement" field
$this->testColumnOnExistingTable();
// 2. Delete "testAutoIncrement" field
$metadata = $this->getMetadata();
$metadata->delete('entityDefs', 'Test', ['fields.testAutoIncrement']);
$metadata->save();
$this->getDataManager()->rebuild();
// 3. Create "testAutoIncrement2" field
$this->updateDefs('Test', 'testAutoIncrement2', [
'type' => 'autoincrement',
]);
$column = $this->getColumnInfo('Test', 'testAutoIncrement');
$this->assertNotEmpty($column);
$this->assertEquals('int', $column['DATA_TYPE']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('10', $column['NUMERIC_PRECISION']);
$this->assertEmpty($column['EXTRA']);
$this->assertEmpty($column['COLUMN_KEY']);
$column2 = $this->getColumnInfo('Test', 'testAutoIncrement2');
$this->assertNotEmpty($column2);
$this->assertEquals('int', $column2['DATA_TYPE']);
$this->assertEquals('NO', $column2['IS_NULLABLE']);
$this->assertEquals('10', $column2['NUMERIC_PRECISION']);
$this->assertEquals('auto_increment', $column2['EXTRA']);
$this->assertEquals('UNI', $column2['COLUMN_KEY']);
}
}

View File

@@ -0,0 +1,110 @@
<?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\integration\Espo\Core\Utils\Database;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Database\Helper;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Util;
use tests\integration\Core\BaseTestCase;
abstract class Base extends BaseTestCase
{
protected ?string $dataFile = 'InitData.php';
protected ?string $pathToFiles = 'Core/Database/customFiles';
protected function beforeSetUp(): void
{}
protected function getColumnInfo($entityName, $fieldName)
{
$pdo = $this->getInjectableFactory()
->create(Helper::class)
->getPDO();
$dbName = $this->getContainer()
->getByClass(Config::class)
->get('database.dbname');
$query = "
SELECT * FROM information_schema.columns
WHERE table_name = '" . Util::toUnderScore($entityName) . "'
AND column_name = '" . Util::toUnderScore($fieldName) . "'
AND table_schema = '" . $dbName . "'
";
$sth = $pdo->prepare($query);
$sth->execute();
return $sth->fetch(\PDO::FETCH_ASSOC);
}
protected function updateDefs($entityName, $fieldName, array $fieldDefs = [], ?array $linkDefs = null)
{
$metadata = $this->getContainer()->getByClass(Metadata::class);
$entityDefs = $metadata->get(['entityDefs', $entityName]);
if (empty($entityDefs)) {
return;
}
$save = false;
if (!empty($fieldDefs)) {
$currentFieldDefs = $entityDefs['fields'][$fieldName] ?? [];
$entityDefs['fields'][$fieldName] = array_merge($currentFieldDefs, $fieldDefs);
$save = true;
}
if (!empty($linkDefs)) {
$currentLinkDefs = $entityDefs['links'][$fieldName] ?? [];
$entityDefs['links'][$fieldName] = array_merge($currentLinkDefs, $linkDefs);
$save = true;
}
if ($save) {
$metadata->set('entityDefs', 'Test', $entityDefs);
$metadata->save();
$this->getDataManager()->rebuild([$entityName]);
}
}
protected function executeQuery($query)
{
$pdo = $this->getInjectableFactory()
->create(Helper::class)
->getPDO();
$sth = $pdo->prepare($query);
$sth->execute();
}
}

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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
#[NoTransaction]
class BooleanFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Test', 'testBoolean');
$this->assertNotEmpty($column);
$this->assertEquals('tinyint', $column['DATA_TYPE']);
$this->assertEquals('0', $column['COLUMN_DEFAULT']);
$this->assertEquals('NO', $column['IS_NULLABLE']);
}
public function testDefaultValue()
{
$this->updateDefs('Test', 'testBoolean', [
'default' => true,
]);
$column = $this->getColumnInfo('Test', 'testBoolean');
$this->assertNotEmpty($column);
$this->assertEquals('tinyint', $column['DATA_TYPE']);
$this->assertEquals('1', $column['COLUMN_DEFAULT']);
$this->assertEquals('NO', $column['IS_NULLABLE']);
}
}

View File

@@ -0,0 +1,57 @@
<?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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
use PHPUnit\Framework\Attributes\DataProvider;
#[NoTransaction]
class CurrencyFieldTest extends Base
{
static public function fieldList(): array
{
return [
['testCurrency', 'double', null, null],
['testCurrencyCurrency', 'varchar', 3, 'utf8mb4_unicode_ci'],
];
}
#[DataProvider('fieldList')]
public function testColumns($fieldName, $type, $length, $collation): void
{
$column = $this->getColumnInfo('Test', $fieldName);
$this->assertNotEmpty($column);
$this->assertEquals($type, $column['DATA_TYPE']);
$this->assertEquals($length, $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals($collation, $column['COLLATION_NAME']);
}
}

View File

@@ -0,0 +1,45 @@
<?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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
#[NoTransaction]
class DateFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Test', 'testDate');
$this->assertNotEmpty($column);
$this->assertEquals('date', $column['DATA_TYPE']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
}
}

View File

@@ -0,0 +1,45 @@
<?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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
#[NoTransaction]
class DateTimeFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Test', 'testDateTime');
$this->assertNotEmpty($column);
$this->assertEquals('datetime', $column['DATA_TYPE']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
}
}

View File

@@ -0,0 +1,47 @@
<?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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
#[NoTransaction]
class FileFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Test', 'testFileId');
$this->assertNotEmpty($column);
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals(17, $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
}

View File

@@ -0,0 +1,45 @@
<?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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
#[NoTransaction]
class FloatFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Test', 'testFloat');
$this->assertNotEmpty($column);
$this->assertEquals('double', $column['DATA_TYPE']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
}
}

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\integration\Espo\Core\Utils\Database;
use Espo\Core\Utils\Database\Helper;
use Doctrine\DBAL\Connection;
use PDO;
use tests\integration\Core\BaseTestCase;
class HelperTest extends BaseTestCase
{
/** @var ?Helper */
protected $helper;
protected function initTest()
{
$this->helper = $this->getInjectableFactory()->create(Helper::class);
}
private function getDatabaseInfo()
{
$pdo = $this->getEntityManager()->getPDO();
$sth = $pdo->prepare("select version()");
$sth->execute();
$version = $sth->fetchColumn();
$type = 'mysql';
if (preg_match('/mariadb/i', $version)) {
$type = 'mariadb';
}
if (preg_match('/[0-9]+\.[0-9]+\.[0-9]+/', $version, $match)) {
$version = $match[0];
}
return [
'type' => $type,
'version' => $version,
];
}
public function testGetDbalConnectionWithConfig()
{
$this->initTest();
$this->assertInstanceOf(Connection::class, $this->helper->getDbalConnection());
}
public function testGetPdoConnectionWithConfig()
{
$this->initTest();
$this->assertInstanceOf(PDO::class, $this->helper->getPDO());
}
public function testGetDatabaseInfo()
{
$this->initTest();
$databaseInfo = $this->getDatabaseInfo();
if (empty($databaseInfo)) {
return;
}
$this->assertEquals($databaseInfo['type'], strtolower($this->helper->getType()));
$this->assertEquals($databaseInfo['version'], $this->helper->getVersion());
}
public function testGetDatabaseType()
{
$this->initTest();
$databaseInfo = $this->getDatabaseInfo();
if (empty($databaseInfo)) {
return;
}
switch ($databaseInfo['type']) {
case 'mysql':
$this->assertEquals('MySQL', $this->helper->getType());
break;
case 'mariadb':
$this->assertEquals('MariaDB', $this->helper->getType());
break;
}
}
}

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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
#[NoTransaction]
class IntegerFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Test', 'testInteger');
$this->assertNotEmpty($column);
$this->assertEquals('int', $column['DATA_TYPE']);
$this->assertEquals(10, $column['NUMERIC_PRECISION']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
}
}

View File

@@ -0,0 +1,83 @@
<?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\integration\Espo\Core\Utils\Database;
use integration\Core\NoTransaction;
#[NoTransaction]
class TextFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Test', 'testText');
$this->assertNotEmpty($column);
$this->assertEquals('mediumtext', $column['COLUMN_TYPE']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
public function testIncreaseColumnLength()
{
$this->updateDefs('Test', 'testText', [
'type' => 'text',
'dbType' => 'longtext',
]);
$column = $this->getColumnInfo('Test', 'testText');
$this->assertNotEmpty($column);
$this->assertEquals('longtext', $column['COLUMN_TYPE']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
public function testReduceColumnLength()
{
$this->updateDefs('Test', 'testText', [
'type' => 'text',
'dbType' => 'longtext',
]);
$this->getContainer()->get('metadata')->delete('entityDefs', 'Test', [
'fields.testText.dbType',
]);
$this->getContainer()->get('metadata')->save();
$this->getDataManager()->rebuildDatabase();
$column = $this->getColumnInfo('Test', 'testText');
$this->assertNotEmpty($column);
$this->assertEquals('longtext', $column['COLUMN_TYPE']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
}

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\integration\Espo\Core\Utils\Database;
use Espo\Core\Utils\Database\Helper as DatabaseHelper;
use integration\Core\NoTransaction;
#[NoTransaction]
class VarcharFieldTest extends Base
{
public function testColumn()
{
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertNotEmpty($column);
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals(100, $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
public function testIncreaseColumnLength()
{
$this->updateDefs('Test', 'testVarchar', [
'maxLength' => 150,
]);
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertNotEmpty($column);
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals('150', $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
public function testReduceColumnLength()
{
$this->updateDefs('Test', 'testVarchar', [
'maxLength' => 50,
]);
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertNotEmpty($column);
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals('100', $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
public function testReduceColumnLength2()
{
$this->updateDefs('Test', 'testVarchar', [
'maxLength' => 50,
'default' => 'test-default',
]);
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertNotEmpty($column);
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals('100', $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
public function testReduceColumnLength3()
{
$this->executeQuery(
"ALTER TABLE test MODIFY COLUMN test_varchar VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL;"
);
$this->updateDefs('Test', 'testVarchar', [
'maxLength' => 50,
'default' => 'test-default',
]);
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertNotEmpty($column);
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals('100', $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertEquals('YES', $column['IS_NULLABLE']);
$this->assertContains($column['COLLATION_NAME'], [
'utf8_unicode_ci',
'utf8mb3_unicode_ci'
]);
}
public function testCollationForExistingColumn()
{
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
$this->executeQuery(
"ALTER TABLE test MODIFY COLUMN test_varchar VARCHAR(". $column['CHARACTER_MAXIMUM_LENGTH'] .") CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL;"
);
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertContains($column['COLLATION_NAME'], [
'utf8_unicode_ci',
'utf8mb3_unicode_ci'
]);
$this->updateDefs('Test', 'testVarchar', [
'maxLength' => 150,
]);
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals('150', $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertContains($column['COLLATION_NAME'], [
'utf8_unicode_ci',
'utf8mb3_unicode_ci'
]);
}
public function testCollationForExistingColumn2()
{
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
$this->executeQuery(
"ALTER TABLE test MODIFY COLUMN test_varchar VARCHAR(". $column['CHARACTER_MAXIMUM_LENGTH'] .") CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL;"
);
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertContains($column['COLLATION_NAME'], [
'utf8_unicode_ci',
'utf8mb3_unicode_ci'
]);
$this->updateDefs('Test', 'testVarchar', [
'default' => 'test-default',
]);
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertContains($column['COLLATION_NAME'], [
'utf8_unicode_ci',
'utf8mb3_unicode_ci'
]);
}
public function testCollationForNewColumn()
{
$this->updateDefs('Test', 'newTestVarchar', [
'type' => 'varchar',
]);
$column = $this->getColumnInfo('Test', 'newTestVarchar');
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
}
public function testDefaultValue()
{
$this->updateDefs('Test', 'testVarchar', [
'default' => 'test-default',
]);
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertEquals('varchar', $column['DATA_TYPE']);
$this->assertEquals('100', $column['CHARACTER_MAXIMUM_LENGTH']);
$this->assertEquals('utf8mb4_unicode_ci', $column['COLLATION_NAME']);
$dbHelper = $this->getInjectableFactory()->create(DatabaseHelper::class);
if (
$dbHelper->getType() == 'MariaDB'
&& version_compare($dbHelper->getVersion(), '10.2.7', '>=')
) {
$this->assertEquals("'test-default'", $column['COLUMN_DEFAULT']);
} else {
$this->assertEquals('test-default', $column['COLUMN_DEFAULT']);
}
}
/**
* Make sure columns not removed.
*/
public function testRemoveField(): void
{
$this->getMetadata()->delete('entityDefs', 'Test', ['fields.testVarchar']);
$this->getMetadata()->save();
$this->getDataManager()->rebuildDatabase();
$column = $this->getColumnInfo('Test', 'testVarchar');
$this->assertTrue((bool) $column);
}
}

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\integration\Espo\Core\Utils\FieldManager;
use Espo\ORM\EntityManager;
use tests\integration\Core\BaseTestCase;
class ArrayTypeTest extends BaseTestCase
{
private $jsonFieldDefs = '{
"name":"testArray",
"label":"TestArray",
"type":"array",
"required":false,
"noEmptyString":false,
"dynamicLogicVisible":null,
"dynamicLogicRequired":null,
"dynamicLogicReadOnly":null,
"dynamicLogicOptions":null,
"options":["option1","option2","option3"],
"translatedOptions":{"option1":"option1","option2":"option2","option3":"option3"},
"tooltipText":null,
"isPersonalData":false,
"inlineEditDisabled":false,
"audited":false,
"readOnly":false,
"tooltip":false
}';
protected function createFieldManager($app = null)
{
if (!$app) {
$app = $this;
}
return $app->getContainer()->get('injectableFactory')->create(
'Espo\\Tools\\FieldManager\\FieldManager'
);
}
public function testCreate()
{
$fieldManager = $this->createFieldManager();
$fieldDefs = get_object_vars(json_decode($this->jsonFieldDefs));
$fieldManager->create('Account', 'testArray', $fieldDefs);
$this->getContainer()->get('dataManager')->rebuild(['Account']);
$app = $this->createApplication();
$metadata = $app->getContainer()->get('metadata');
$savedFieldDefs = $metadata->get('entityDefs.Account.fields.cTestArray');
$this->assertArrayHasKey('type', $savedFieldDefs);
$this->assertArrayHasKey('isCustom', $savedFieldDefs);
$this->assertArrayHasKey('options', $savedFieldDefs);
$this->assertEquals('array', $savedFieldDefs['type']);
$this->assertTrue($savedFieldDefs['isCustom']);
$entityManager = $app->getContainer()->getByClass(EntityManager::class);
$account = $entityManager->getNewEntity('Account');
$account->set([
'name' => 'Test',
'cTestArray' => ['option1', 'option3']
]);
$entityManager->saveEntity($account);
$account = $entityManager->getEntityById('Account', $account->getId());
$this->assertEquals(['option1', 'option3'], $account->get('cTestArray'));
}
public function testUpdate()
{
$this->testCreate();
$app = $this->createApplication();
$fieldManager = $this->createFieldManager($app);
$fieldDefs = get_object_vars(json_decode($this->jsonFieldDefs));
$fieldDefs['required'] = true;
$fieldManager->update('Account', 'cTestArray', $fieldDefs);
$this->getContainer()->get('dataManager')->rebuild(['Account']);
$app = $this->createApplication();
$metadata = $app->getContainer()->get('metadata');
$savedFieldDefs = $metadata->get('entityDefs.Account.fields.cTestArray');
$this->assertTrue($savedFieldDefs['required']);
$entityManager = $app->getContainer()->getByClass(EntityManager::class);
$account = $entityManager->getNewEntity('Account');
$account->set([
'name' => 'Test',
]);
$entityManager->saveEntity($account);
}
}

View File

@@ -0,0 +1,141 @@
<?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\integration\Espo\Core\Utils\FieldManager;
use Espo\Core\Utils\Metadata;
use Espo\ORM\EntityManager;
use tests\integration\Core\BaseTestCase;
class EnumTypeTest extends BaseTestCase
{
private $jsonFieldDefs = '{
"type":"enum",
"required":true,
"dynamicLogicVisible":null,
"dynamicLogicRequired":null,
"dynamicLogicReadOnly":null,
"dynamicLogicOptions":null,
"name":"testEnum",
"label":"TestEnum",
"audited":true,
"options":["option1","option2","option3"],
"translatedOptions":{"option1":"option1","option2":"option2","option3":"option3"},
"default":"option2",
"tooltipText":"",
"isPersonalData":false,
"isSorted":false,
"readOnly":false,
"tooltip":false
}';
protected function createFieldManager($app = null)
{
if (!$app) {
$app = $this;
}
return $app->getContainer()->get('injectableFactory')->create(
'Espo\\Tools\\FieldManager\\FieldManager'
);
}
public function testCreate()
{
$fieldManager = $this->createFieldManager();
$fieldDefs = get_object_vars(json_decode($this->jsonFieldDefs));
$fieldManager->create('Account', 'testEnum', $fieldDefs);
$this->getDataManager()->rebuild(['Account']);
$app = $this->createApplication();
$metadata = $app->getContainer()->getByClass(Metadata::class);
$savedFieldDefs = $metadata->get('entityDefs.Account.fields.cTestEnum');
$this->assertEquals('enum', $savedFieldDefs['type']);
$this->assertEquals('option2', $savedFieldDefs['default']);
$this->assertTrue($savedFieldDefs['required']);
$this->assertTrue($savedFieldDefs['isCustom']);
$this->assertTrue($savedFieldDefs['audited']);
$entityManager = $app->getContainer()->getByClass(EntityManager::class);
$account = $entityManager->getNewEntity('Account');
$account->set([
'name' => 'Test',
'cTestEnum' => 'option1',
]);
$entityManager->saveEntity($account);
$account = $entityManager->getEntityById('Account', $account->getId());
$this->assertEquals('option1', $account->get('cTestEnum'));
}
public function testUpdate()
{
$this->testCreate();
$app = $this->createApplication();
$fieldManager = $this->createFieldManager($app);
$fieldDefs = get_object_vars(json_decode($this->jsonFieldDefs));
$fieldDefs['required'] = false;
$fieldDefs['default'] = 'option3';
$fieldDefs['readOnly'] = true;
$fieldManager->update('Account', 'cTestEnum', $fieldDefs);
$this->getDataManager()->rebuild(['Account']);
$app = $this->createApplication();
$metadata = $app->getContainer()->get('metadata');
$savedFieldDefs = $metadata->get('entityDefs.Account.fields.cTestEnum');
$this->assertFalse($savedFieldDefs['required']);
$this->assertEquals('option3', $savedFieldDefs['default']);
$this->assertTrue($savedFieldDefs['audited']);
$this->assertTrue($savedFieldDefs['readOnly']);
$entityManager = $app->getContainer()->getByClass(EntityManager::class);
$account = $entityManager->getNewEntity('Account');
$account->set([
'name' => 'New Test',
]);
$entityManager->saveEntity($account);
$account = $entityManager->getEntityById('Account', $account->getId());
$this->assertEquals('option3', $account->get('cTestEnum'));
}
}

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\integration\Espo\Core\Utils\FieldManager;
use tests\integration\Core\BaseTestCase;
class VarcharTypeTest extends BaseTestCase
{
private $jsonFieldDefs = '{
"type":"varchar",
"required":true,
"trim":true,
"dynamicLogicVisible":null,
"dynamicLogicRequired":null,
"dynamicLogicReadOnly":null,
"name":"testVarchar",
"label":"TestVarchar",
"tooltipText":"",
"isPersonalData":false,
"default":"",
"maxLength":null,
"audited":false,
"readOnly":false,
"tooltip":false
}';
protected function createFieldManager($app = null)
{
if (!$app) {
$app = $this;
}
return $app->getContainer()->get('injectableFactory')->create(
'Espo\\Tools\\FieldManager\\FieldManager'
);
}
public function testCreate()
{
$fieldManager = $this->createFieldManager();
$fieldDefs = get_object_vars(json_decode($this->jsonFieldDefs));
$fieldManager->create('Account', 'testVarchar', $fieldDefs);
$this->getContainer()->get('dataManager')->rebuild(['Account']);
$app = $this->createApplication();
$metadata = $app->getContainer()->get('metadata');
$savedFieldDefs = $metadata->get('entityDefs.Account.fields.cTestVarchar');
$this->assertArrayHasKey('type', $savedFieldDefs);
$this->assertArrayHasKey('isCustom', $savedFieldDefs);
$this->assertEquals('varchar', $savedFieldDefs['type']);
$this->assertTrue($savedFieldDefs['required']);
$this->assertTrue($savedFieldDefs['isCustom']);
$entityManager = $app->getContainer()->get('entityManager');
$account = $entityManager->getEntity('Account');
$account->set([
'name' => 'Test',
'cTestVarchar' => 'test-value'
]);
$entityManager->saveEntity($account);
$account = $entityManager->getEntity('Account', $account->getId());
$this->assertEquals('test-value', $account->get('cTestVarchar'));
}
public function testUpdate()
{
$this->testCreate();
$app = $this->createApplication();
$fieldManager = $this->createFieldManager($app);
$fieldDefs = get_object_vars(json_decode($this->jsonFieldDefs));
$fieldDefs['required'] = false;
$fieldDefs['default'] = 'default-value';
$fieldManager->update('Account', 'cTestVarchar', $fieldDefs);
$this->getContainer()->get('dataManager')->rebuild(['Account']);
$app = $this->createApplication();
$metadata = $app->getContainer()->get('metadata');
$savedFieldDefs = $metadata->get('entityDefs.Account.fields.cTestVarchar');
$this->assertFalse($savedFieldDefs['required']);
$this->assertEquals('default-value', $savedFieldDefs['default']);
$entityManager = $app->getContainer()->get('entityManager');
$account = $entityManager->getEntity('Account');
$account->set([
'name' => 'New Test',
]);
$entityManager->saveEntity($account);
$account = $entityManager->getEntity('Account', $account->getId());
$this->assertEquals('default-value', $account->get('cTestVarchar'));
}
}

View File

@@ -0,0 +1,110 @@
<?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\integration\Espo\Core\Utils;
use Espo\Core\Utils\Json;
use Espo\Core\Utils\Metadata;
use tests\integration\Core\BaseTestCase;
class MetadataTest extends BaseTestCase
{
private $filePath1 = 'custom/Espo/Custom/Resources/metadata/app/rebuild.json';
private $filePath2 = 'custom/Espo/Custom/Resources/metadata/recordDefs/Note.json';
protected function tearDown(): void
{
$this->getFileManager()->removeFile($this->filePath1);
$this->getFileManager()->removeFile($this->filePath2);
parent::tearDown();
}
public function testAppend1(): void
{
$initial = $this->getMetadata()->get(['app', 'rebuild', 'actionClassNameList']);
$contents1 = Json::encode(
(object) [
'actionClassNameList' => [
"\\Espo\\Core\\Rebuild\\Actions\\ScheduledJobs",
]
]
);
$contents2 = Json::encode(
(object) [
'readLoaderClassNameList' => [
"\\Espo\\Classes\\FieldProcessing\\Note\\AdditionalFieldsLoader",
]
]
);
$this->createDirForFile($this->filePath1);
$this->getFileManager()->putContents($this->filePath1, $contents1);
$this->getFileManager()->putContents($this->filePath2, $contents2);
$this->getDataManager()->clearCache();
$this->getDataManager()->rebuildMetadata();
$this->reCreateApplication(reuse: true);
$metadata = $this->getContainer()->getByClass(Metadata::class);
$this->assertSame(
array_merge(
$initial,
["\\Espo\\Core\\Rebuild\\Actions\\ScheduledJobs"]
),
$metadata->get(['app', 'rebuild', 'actionClassNameList'])
);
$this->assertSame(
[
"Espo\\Classes\\FieldProcessing\\Note\\AdditionalFieldsLoader",
"\\Espo\\Classes\\FieldProcessing\\Note\\AdditionalFieldsLoader",
],
$metadata->get(['recordDefs', 'Note', 'readLoaderClassNameList'])
);
$this->getFileManager()->removeFile($this->filePath1);
$this->getFileManager()->removeFile($this->filePath2);
$this->getDataManager()->clearCache();
}
private function createDirForFile(string $filePath): void
{
$prevFolder = '';
foreach (array_slice(explode('/', $filePath), 0, -1) as $folder) {
$prevFolder .= $folder . '/';
$this->getFileManager()->mkdir(substr($prevFolder, 0, -1));
}
}
}

View File

@@ -0,0 +1,69 @@
<?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\integration\Espo\Core\Utils\ScheduledJob;
use Espo\Core\Utils\ScheduledJob\Populator;
use Espo\Entities\ScheduledJob;
use Espo\ORM\Query\DeleteBuilder;
use tests\integration\Core\BaseTestCase;
class PopulatorTest extends BaseTestCase
{
public function testPopulate(): void
{
$deleteQuery = DeleteBuilder::create()
->from(ScheduledJob::ENTITY_TYPE)
->where([
ScheduledJob::FIELD_JOB => 'Cleanup',
])
->build();
$this->getEntityManager()->getQueryExecutor()->execute($deleteQuery);
$this->assertEquals(0, $this->getJobCount());
$populator = $this->getInjectableFactory()->create(Populator::class);
$populator->populate();
$this->assertEquals(1, $this->getJobCount());
}
private function getJobCount(): int
{
return $this->getEntityManager()
->getRDBRepositoryByClass(ScheduledJob::class)
->where([
ScheduledJob::FIELD_JOB => 'Cleanup',
])
->count();
}
}

View File

@@ -0,0 +1,271 @@
<?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\integration\Espo\Currency;
use Espo\Core\Exceptions\Error;
use Espo\Core\Field\Date;
use Espo\Core\Formula\Manager as FormulaManager;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Modules\Crm\Entities\Opportunity;
use Espo\Tools\Currency\RateEntryProvider;
use Espo\Tools\Currency\RateService;
use Espo\Core\Currency\Rates;
use Espo\Core\Field\Currency;
use Espo\Core\Utils\Config\ConfigWriter;
use Espo\Tools\Currency\SyncManager;
use integration\Core\NoTransaction;
use tests\integration\Core\BaseTestCase;
class CurrencyTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testSetCurrencyRates(): void
{
$factory = $this->getInjectableFactory();
$configWriter = $factory->create(ConfigWriter::class);
$configWriter->set('currencyList', ['USD', 'EUR']);
$configWriter->set('defaultCurrency', 'USD');
$configWriter->set('baseCurrency', 'USD');
$configWriter->set('currencyRates', [
'EUR' => 1.2,
]);
$configWriter->save();
$this->getInjectableFactory()->create(SyncManager::class)->sync();
$service = $factory->create(RateService::class);
$rates = Rates::fromAssoc(['EUR' => 1.3], '___');
$service->set($rates);
$newRates = $service->get();
$this->assertEquals(1.3, $newRates->getRate('EUR'));
}
/**
* @throws Error
*/
#[NoTransaction]
public function testDecimal1(): void
{
$this->getMetadata()->set('entityDefs', 'Lead', [
'fields' => [
'testCurrency' => [
'type' => 'currency',
'decimal' => true,
]
]
]);
$this->getMetadata()->save();
$this->getDataManager()->rebuild();
$this->reCreateApplication();
$em = $this->getEntityManager();
$value = Currency::create('10.1', 'USD')
->add(Currency::create('0.1', 'USD'));
$lead = $em->getRDBRepositoryByClass(Lead::class)->getNew();
$lead->setValueObject('testCurrency', $value);
$value = $lead->getValueObject('testCurrency');
$this->assertInstanceOf(Currency::class, $value);
$this->assertEquals('10.2000', $value->getAmountAsString());
$this->getEntityManager()->saveEntity($lead);
/** @var Lead $lead */
$lead = $this->getEntityManager()->getEntityById(Lead::ENTITY_TYPE, $lead->getId());
$value = $lead->getValueObject('testCurrency');
$this->assertInstanceOf(Currency::class, $value);
$this->assertEquals('10.2000', $value->getAmountAsString());
$this->assertEquals(0, $value->compare(Currency::create('10.2', 'USD')));
//
$lead = $em->getRDBRepositoryByClass(Lead::class)->getNew();
$value = Currency::create('10', 'USD');
$lead->setValueObject('testCurrency', $value);
$value = $lead->getValueObject('testCurrency');
$this->assertInstanceOf(Currency::class, $value);
$this->assertEquals('10.0000', $value->getAmountAsString());
//
$lead = $em->getRDBRepositoryByClass(Lead::class)->getNew();
$lead->setValueObject('testCurrency', null);
$value = $lead->getValueObject('testCurrency');
$this->assertEquals(null, $value);
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testFormulaConvert(): void
{
$formulaManager = $this->getContainer()->getByClass(FormulaManager::class);
$em = $this->getEntityManager();
$configWriter = $this->getInjectableFactory()->create(ConfigWriter::class);
$configWriter->set('currencyList', ['USD', 'EUR']);
$configWriter->set('defaultCurrency', 'USD');
$configWriter->set('baseCurrency', 'USD');
$configWriter->save();
$syncManager = $this->getInjectableFactory()->create(SyncManager::class);
$syncManager->sync();
$rate = $this->getInjectableFactory()->create(RateEntryProvider::class)
->prepareNew('EUR', Date::createToday()->addDays(-2));
$rate->setRate('2.0');
$em->saveEntity($rate);
$syncManager->refreshCache();
$script = "ext\\currency\\convert('0.5', 'EUR')";
$value = $formulaManager->run($script);
$this->assertEquals(1.0, (float) $value);
$script = "ext\\currency\\convert('0.5', 'EUR', 'USD')";
$value = $formulaManager->run($script);
$this->assertEquals(1.0, (float) $value);
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testDefaultCurrencyJoinMode(): void
{
$configWriter = $this->getInjectableFactory()->create(ConfigWriter::class);
$configWriter->set('currencyNoJoinMode', false);
$configWriter->set('currencyList', ['USD', 'EUR']);
$configWriter->set('defaultCurrency', 'USD');
$configWriter->set('baseCurrency', 'EUR');
$configWriter->save();
$this->getInjectableFactory()->create(SyncManager::class)->sync();
$rateService = $this->getInjectableFactory()->create(RateService::class);
$rateService->set(Rates::fromAssoc([
'USD' => 0.5,
], 'EUR'));
// Full rebuild caused a problem with the test transaction in PG.
$this->getDataManager()->rebuildMetadata();
$this->reCreateApplication(reuse: true);
$em = $this->getEntityManager();
$opp = $em->getRDBRepositoryByClass(Opportunity::class)->getNew();
$opp->setAmount(Currency::create('10.0', 'USD'));
$em->saveEntity($opp);
$em->refreshEntity($opp);
$this->assertEquals(10, $opp->get('amountConverted'));
//
$opp = $em->getRDBRepositoryByClass(Opportunity::class)->getNew();
$opp->setAmount(Currency::create('10.0', 'EUR'));
$em->saveEntity($opp);
$em->refreshEntity($opp);
$this->assertEquals(20, $opp->get('amountConverted'));
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testDefaultCurrencyNoJoinMode(): void
{
$configWriter = $this->getInjectableFactory()->create(ConfigWriter::class);
$configWriter->set('currencyNoJoinMode', true);
$configWriter->set('currencyList', ['USD', 'EUR']);
$configWriter->set('defaultCurrency', 'USD');
$configWriter->set('baseCurrency', 'EUR');
$configWriter->save();
$this->getInjectableFactory()->create(SyncManager::class)->sync();
$rateService = $this->getInjectableFactory()->create(RateService::class);
$rateService->set(Rates::fromAssoc([
'USD' => 0.5,
], 'EUR'));
// Full rebuild caused a problem with the test transaction in PG.
$this->getDataManager()->rebuildMetadata();
$this->reCreateApplication(reuse: true);
$em = $this->getEntityManager();
$opp = $em->getRDBRepositoryByClass(Opportunity::class)->getNew();
$opp->setAmount(Currency::create('10.0', 'USD'));
$em->saveEntity($opp);
$em->refreshEntity($opp);
$this->assertEquals(10, $opp->get('amountConverted'));
//
$opp = $em->getRDBRepositoryByClass(Opportunity::class)->getNew();
$opp->setAmount(Currency::create('10.0', 'EUR'));
$em->saveEntity($opp);
$em->refreshEntity($opp);
$this->assertEquals(20, $opp->get('amountConverted'));
}
}

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\integration\Espo\Email;
class EmailEntityTest extends \tests\integration\Core\BaseTestCase
{
public function testFromFields()
{
$entityManager = $this->getContainer()->get('entityManager');
$email = $entityManager->getEntity('Email');
$email->set('fromString', 'Test Hello <test@test.com>');
$this->assertEquals('test@test.com', $email->get('fromAddress'));
$this->assertEquals('Test Hello', $email->get('fromName'));
}
public function testReplyToFields()
{
$entityManager = $this->getContainer()->get('entityManager');
$email = $entityManager->getEntity('Email');
$email->set('replyToString', 'Test Hello <test@test.com>; Man Test <man@test.com>');
$this->assertEquals('test@test.com', $email->get('replyToAddress'));
$this->assertEquals('Test Hello', $email->get('replyToName'));
}
}

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\integration\Espo\Email;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Select\SearchParams;
use Espo\Entities\Email;
use integration\Core\NoTransaction;
use tests\integration\Core\BaseTestCase;
class SearchByEmailAddressTest extends BaseTestCase
{
public function testSearchByEmailAddress()
{
$entityManager = $this->getEntityManager();
$email = $entityManager->getNewEntity('Email');
$email->set('from', 'test@test.com');
$email->set('status', 'Archived');
$entityManager->saveEntity($email);
$emailService = $this->getApplication()
->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Email::class);
$result = $emailService->find(
SearchParams::fromRaw([
'where' => [
[
'type' => 'equals',
'attribute' => 'emailAddress',
'value' => 'test@test.com'
]
]
])
);
$this->assertEquals(1, count($result->getCollection()));
}
/**
* Full-text search index is not applied for uncommited data.
*/
#[NoTransaction]
public function testTextSearch()
{
$entityManager = $this->getEntityManager();
$email = $entityManager->getNewEntity('Email');
$email->set('from', 'test@test.com');
$email->set('status', 'Archived');
$email->set('name', 'Improvements to our Privacy Policy');
$email->set('body', 'name abc test');
$entityManager->saveEntity($email);
$emailService = $this->getApplication()
->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Email::class);
$result = $emailService->find(
SearchParams::fromRaw([
'textFilter' => 'name abc'
])
);
$this->assertEquals(1, count($result->getCollection()));
$result = $emailService->find(
SearchParams::fromRaw([
'textFilter' => 'Improvements to our Privacy Policy'
])
);
$this->assertEquals(1, count($result->getCollection()));
}
}

View File

@@ -0,0 +1,100 @@
<?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\integration\Espo\Event;
use Espo\Core\Field\DateTime;
use Espo\Core\Field\DateTimeOptional;
use Espo\Modules\Crm\Entities\Call;
use Espo\Modules\Crm\Entities\Meeting;
use tests\integration\Core\BaseTestCase;
class EventTest extends BaseTestCase
{
public function testMeetingAutomaticDateEnd(): void
{
$em = $this->getEntityManager();
$meeting = $em->getRDBRepositoryByClass(Meeting::class)->getNew();
$meeting->setDateStart(DateTimeOptional::fromString('2030-01-01 10:00'));
$meeting->setDuration(60);
$em->saveEntity($meeting);
$em->refreshEntity($meeting);
$this->assertEquals('2030-01-01 10:01:00', $meeting->getDateEnd()?->toString());
//
$meeting = $em->getRDBRepositoryByClass(Meeting::class)->getNew();
$meeting->setDateStart(DateTimeOptional::fromString('2030-01-01 10:00'));
$meeting->setDateEnd(DateTimeOptional::fromString('2030-01-01 11:00'));
$em->saveEntity($meeting);
$em->refreshEntity($meeting);
$this->assertEquals('2030-01-01 11:00:00', $meeting->getDateEnd()?->toString());
//
$meeting = $em->getRDBRepositoryByClass(Meeting::class)->getNew();
$meeting->setIsAllDay(true);
$meeting->setDateStart(DateTimeOptional::fromString('2030-01-01'));
$meeting->setDuration(3600 * 24 * 2);
$em->saveEntity($meeting);
$em->refreshEntity($meeting);
$this->assertEquals('2030-01-02', $meeting->getDateEnd()?->toString());
//
$meeting = $em->getRDBRepositoryByClass(Meeting::class)->getNew();
$meeting->setIsAllDay(true);
$meeting->setDateStart(DateTimeOptional::fromString('2030-01-01'));
$meeting->setDateEnd(DateTimeOptional::fromString('2030-01-02'));
$em->saveEntity($meeting);
$em->refreshEntity($meeting);
$this->assertEquals('2030-01-02', $meeting->getDateEnd()?->toString());
}
public function testCallAutomaticDateEnd(): void
{
$em = $this->getEntityManager();
$call = $em->getRDBRepositoryByClass(Call::class)->getNew();
$call->setDateStart(DateTime::fromString('2030-01-01 10:00'));
$call->setDuration(60);
$em->saveEntity($call);
$em->refreshEntity($call);
$this->assertEquals('2030-01-01 10:01:00', $call->getDateEnd()?->toString());
}
}

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\integration\Espo\Export;
use Espo\ORM\EntityManager;
use Espo\Core\{
FileStorage\Manager as FileStorageManager,
Select\SearchParams,
Select\Where\Item as WhereItem,
};
use Espo\Tools\Export\{
Factory,
Params,
};
class ExportTest extends \tests\integration\Core\BaseTestCase
{
/**
* @var EntityManager
*/
private $entityManager;
/**
* @var Factory
*/
private $factory;
/**
* @var FileStorageManager
*/
private $fileStorageManager;
protected function setUp(): void
{
parent::setUp();
$this->entityManager = $this->getContainer()->get('entityManager');
$this->factory = $this->getContainer()
->get('injectableFactory')
->create(Factory::class);
$this->fileStorageManager = $this->getContainer()->get('fileStorageManager');
}
public function testCsvWithFieldList(): void
{
$user =$this->entityManager->createEntity('User', [
'id' => 'user-id',
'userName' => 'user',
'lastName' => 'User',
]);
$this->entityManager->createEntity('Task', [
'id' => '1',
'name' => 'test-1',
'assignedUserId' => $user->getId(),
]);
$this->entityManager->createEntity('Task', [
'id' => '2',
'name' => 'test-2',
'assignedUserId' => $user->getId(),
]);
$this->entityManager->createEntity('Task', [
'id' => '3',
'name' => 'test-3',
]);
$searchParams = SearchParams
::create()
->withWhere(WhereItem::fromRaw([
'type' => 'equals',
'attribute' => 'assignedUserId',
'value' => $user->getId(),
]));
$params = Params
::create('Task')
->withFieldList([
'name',
'assignedUser',
])
->withSearchParams($searchParams)
->withFormat('csv');
$export = $this->factory->create();
$attachmentId = $export
->setParams($params)
->run()
->getAttachmentId();
$attachment = $this->entityManager->getEntityById('Attachment', $attachmentId);
$contents = $this->fileStorageManager->getContents($attachment);
$expectedContents =
"name,assignedUserId,assignedUserName\n" .
"test-2,user-id,User\n" .
"test-1,user-id,User\n";
$this->assertEquals($expectedContents, $contents);
}
public function testCsvWithAttributeList(): void
{
$user = $this->entityManager->createEntity('User', [
'id' => 'user-id',
'userName' => 'user',
'lastName' => 'User',
]);
$this->entityManager->createEntity('Task', [
'id' => '1',
'name' => 'test-1',
'assignedUserId' => $user->getId(),
]);
$this->entityManager->createEntity('Task', [
'id' => '2',
'name' => 'test-2',
'assignedUserId' => $user->getId(),
]);
$params = Params
::create('Task')
->withAttributeList([
'id',
'name',
'assignedUserId',
])
->withAccessControl(false)
->withFormat('csv');
$export = $this->factory->create();
$attachmentId = $export
->setParams($params)
->run()
->getAttachmentId();
$attachment = $this->entityManager->getEntityById('Attachment', $attachmentId);
$contents = $this->fileStorageManager->getContents($attachment);
$expectedContents =
"id,name,assignedUserId\n" .
"2,test-2,user-id\n" .
"1,test-1,user-id\n";
$this->assertEquals($expectedContents, $contents);
}
public function testCsvCollection(): void
{
$user = $this->entityManager->createEntity('User', [
'id' => 'user-id',
'userName' => 'user',
'lastName' => 'User',
]);
$this->entityManager->createEntity('Task', [
'id' => '1',
'name' => 'test-1',
'assignedUserId' => $user->getId(),
]);
$this->entityManager->createEntity('Task', [
'id' => '2',
'name' => 'test-2',
'assignedUserId' => $user->getId(),
]);
$this->entityManager->createEntity('Task', [
'id' => '3',
'name' => 'test-3',
]);
$collection = $this->entityManager
->getRDBRepository('Task')
->where([
'assignedUserId' => $user->getId(),
])
->order('id', 'ASC')
->find();
$params = Params
::create('Task')
->withAttributeList([
'name',
'assignedUserId',
])
->withFieldList([
'name',
'assignedUser',
])
->withFormat('csv');
$export = $this->factory->create();
$attachmentId = $export
->setParams($params)
->setCollection($collection)
->run()
->getAttachmentId();
$attachment = $this->entityManager->getEntityById('Attachment', $attachmentId);
$contents = $this->fileStorageManager->getContents($attachment);
$expectedContents =
"name,assignedUserId\n" .
"test-1,user-id\n" .
"test-2,user-id\n";
$this->assertEquals($expectedContents, $contents);
}
}

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\integration\Espo\Extension;
use Espo\Core\Upgrades\ExtensionManager;
use Espo\Entities\User;
use integration\Core\NoTransaction;
use tests\integration\Core\BaseTestCase;
class GeneralTest extends BaseTestCase
{
protected ?string $password = '1';
protected $packagePath = 'Extension/General.zip';
protected function setUp(): void
{
parent::setUp();
$this->createUser([
'type' => User::TYPE_ADMIN,
'userName' => 'admin',
'lastName' => 'Admin',
'password' => $this->password,
]);
}
protected function beforeSetUp(): void
{
$this->fullReset();
}
/**
* If this test fails, an instance may become broken for consecutive test runs.
*/
#[NoTransaction]
public function testExtensionUploadInstallUninstallDelete(): void
{
$this->authenticate('admin');
$extensionId = $this->testUninstall();
$extensionManager = new ExtensionManager($this->getContainer());
$extensionManager->delete(['id' => $extensionId]);
$this->assertFileDoesNotExist('data/.backup/extensions/' . $extensionId);
$this->assertFileDoesNotExist('data/upload/extensions/' . $extensionId);
$this->assertFileDoesNotExist('data/upload/extensions/' . $extensionId . 'z');
$this->assertFileDoesNotExist('application/Espo/Modules/Test');
$this->assertFileDoesNotExist('application/Espo/Modules/Test/Resources/metadata/scopes/TestEntity.json');
$this->assertFileDoesNotExist('client/modules/test');
$this->assertFileDoesNotExist('client/modules/test/src/views/test-entity/fields/custom-type.js');
$this->assertFileExists('vendor/symfony');
$this->assertFileExists('extension.php');
$this->assertFileExists('upgrade.php');
}
private function testUpload(): string
{
$fileData = file_get_contents($this->normalizePath($this->packagePath));
$fileData = 'data:application/zip;base64,' . base64_encode($fileData);
$extensionManager = new ExtensionManager($this->getContainer());
$extensionId = $extensionManager->upload($fileData);
$this->assertStringMatchesFormat('%x', $extensionId);
$this->assertFileExists('data/upload/extensions/' . $extensionId . 'z');
$this->assertFileExists('data/upload/extensions/' . $extensionId);
return $extensionId;
}
private function testInstall(): string
{
$extensionId = $this->testUpload();
$extensionManager = new ExtensionManager($this->getContainer());
$extensionManager->install(['id' => $extensionId]);
$this->assertFileExists('data/upload/extensions/' . $extensionId . 'z');
$this->assertFileDoesNotExist('data/upload/extensions/' . $extensionId);
$this->assertFileExists('data/.backup/extensions/' . $extensionId);
$this->assertFileExists('application/Espo/Modules/Test');
$this->assertFileExists('application/Espo/Modules/Test/Resources/metadata/scopes/TestEntity.json');
$this->assertFileExists('client/modules/test');
$this->assertFileExists('client/modules/test/src/views/test-entity/fields/custom-type.js');
$this->assertFileDoesNotExist('vendor/symfony');
$this->assertFileDoesNotExist('extension.php');
$this->assertFileDoesNotExist('upgrade.php');
return $extensionId;
}
private function testUninstall(): string
{
$extensionId = $this->testInstall();
$extensionManager = new ExtensionManager($this->getContainer());
$extensionManager->uninstall(['id' => $extensionId]);
$this->assertFileDoesNotExist('data/.backup/extensions/' . $extensionId);
$this->assertFileDoesNotExist('data/upload/extensions/' . $extensionId);
$this->assertFileExists('data/upload/extensions/' . $extensionId . 'z');
$this->assertFileDoesNotExist('application/Espo/Modules/Test');
$this->assertFileDoesNotExist('application/Espo/Modules/Test/Resources/metadata/scopes/TestEntity.json');
$this->assertFileDoesNotExist('client/modules/test');
$this->assertFileDoesNotExist('client/modules/test/src/views/test-entity/fields/custom-type.js');
$this->assertFileExists('vendor/symfony');
$this->assertFileExists('extension.php');
$this->assertFileExists('upgrade.php');
return $extensionId;
}
}

View File

@@ -0,0 +1,93 @@
<?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\integration\Espo\GlobalSearch;
use Espo\Tools\GlobalSearch\Service;
use tests\integration\Core\BaseTestCase;
class GlobalSearchTest extends BaseTestCase
{
public function testSearch1(): void
{
$em = $this->getEntityManager();
$team = $em->createEntity('Team', [
'name' => 'test',
]);
$contact = $em->createEntity('Contact', [
'lastName' => '1',
'teamsIds' => [$team->getId()],
]);
$account = $em->createEntity('Account', [
'name' => '1',
'teamsIds' => [$team->getId()],
]);
$account = $em->createEntity('Account', [
'name' => '2',
'teamsIds' => [$team->getId()],
]);
$account = $em->createEntity('Account', [
'name' => '1',
]);
$this->createUser([
'userName' => 'tester',
'teamsIds' => [$team->getId()],
], [
'data' => [
'Account' => [
'create' => 'no',
'read' => 'team',
'edit' => 'no',
'delete' => 'no',
'stream' => 'no',
],
'Contact' => [
'create' => 'no',
'read' => 'team',
'edit' => 'no',
'delete' => 'no',
'stream' => 'no',
],
],
]);
$this->authenticate('tester');
$service = $this->getInjectableFactory()->create(Service::class);
$result = $service->find('1', 0, 10);
$this->assertEquals(2, count($result->getCollection()));
}
}

View File

@@ -0,0 +1,86 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\LeadCapture;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Entities\LeadCapture;
use Espo\ORM\EntityManager;
use Espo\Tools\LeadCapture\CaptureService;
use tests\integration\Core\BaseTestCase;
class LeadCaptureTest extends BaseTestCase
{
public function testCapture():void
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$targetList = $entityManager->getNewEntity('TargetList');
$entityManager->saveEntity($targetList);
$team = $entityManager->getNewEntity('Team');
$entityManager->saveEntity($team);
$recordService = $this->getContainer()->getByClass(ServiceContainer::class)->getByClass(LeadCapture::class);
$service = $this->getInjectableFactory()->create(CaptureService::class);
$leadCaptureData = (object) [
'name' => 'test',
'subscribeToTargetList' => true,
'targetListId' => $targetList->getId(),
'targetTeamId' => $team->getId(),
'fieldList' => ['name', 'emailAddress'],
'leadSource' => 'Web Site'
];
$leadCapture = $recordService->create($leadCaptureData, CreateParams::create());
$this->assertNotEmpty($leadCapture->get('apiKey'));
$data = (object) [
'firstName' => 'Test',
'lastName' => 'Tester',
'emailAddress' => 'test@tester.com'
];
$service->capture($leadCapture->get('apiKey'), $data);
$lead = $entityManager
->getRDBRepository('Lead')
->where(['emailAddress' => 'test@tester.com'])
->findOne();
$this->assertNotNull($lead);
$this->assertEquals('Web Site', $lead->get('source'));
$this->assertTrue($entityManager->getRelation($lead, 'teams')->isRelatedById($team->getId()));
$this->assertTrue($entityManager->getRelation($lead, 'targetLists')->isRelatedById($targetList->getId()));
}
}

View File

@@ -0,0 +1,138 @@
<?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\integration\Espo\Note;
use Espo\{
Tools\Stream\Service as StreamService,
ORM\EntityManager,
};
class AclTest extends \tests\integration\Core\BaseTestCase
{
public function testProcessNoteAcl()
{
/* @var $em EntityManager*/
$em = $this->getContainer()->get('entityManager');
/* @var $streamService StreamService*/
$streamService = $this->getContainer()->get('injectableFactory')->create(StreamService::class);
$team1 = $em->createEntity('Team', [
'name' => 'team-1',
]);
$team2 = $em->createEntity('Team', [
'name' => 'team-2',
]);
$user1 = $em->createEntity('User', [
'userName' => 'user-1',
'lastName' => 'user-1',
]);
$user2 = $em->createEntity('User', [
'userName' => 'user-2',
'lastName' => 'user-2',
]);
$account = $em->createEntity('Account', [
]);
// Opportunity
$opportunity = $em->createEntity('Opportunity', [
'assignedUserId' => $user1->getId(),
'teamsIds' => [$team1->getId()],
'accountId' => $account->getId(),
]);
$streamService->noteCreate($opportunity);
$note1 = $em
->getRDBRepository('Note')
->where([
'type' => 'Create',
'parentId' => $opportunity->getId(),
'parentType' => $opportunity->getEntityType(),
])
->findOne();
$this->assertEquals([$team1->getId()], $note1->getLinkMultipleIdList('teams'));
$this->assertEquals([$user1->getId()], $note1->getLinkMultipleIdList('users'));
$opportunity->set([
'assignedUserId' => $user2->getId(),
'teamsIds' => [$team2->getId()],
]);
$em->saveEntity($opportunity);
$note1 = $em->getEntityById('Note', $note1->getId());
$this->assertEquals([$team2->getId()], $note1->getLinkMultipleIdList('teams'));
$this->assertEquals([$user2->getId()], $note1->getLinkMultipleIdList('users'));
// Meeting
$meeting = $em->createEntity('Meeting', [
'usersIds' => [$user1->getId()],
'teamsIds' => [$team1->getId()],
'parentId' => $account->getId(),
'parentType' => $account->getEntityType(),
]);
$streamService->noteRelate($meeting, $account);
$note2 = $em
->getRDBRepository('Note')
->where([
'type' => 'Relate',
'relatedId' => $meeting->getId(),
'relatedType' => $meeting->getEntityType(),
])
->findOne();
$this->assertEquals([$team1->getId()], $note2->getLinkMultipleIdList('teams'));
$this->assertEquals([$user1->getId()], $note2->getLinkMultipleIdList('users'));
$meeting->set([
'usersIds' => [$user2->getId()],
'teamsIds' => [$team2->getId()],
]);
$em->saveEntity($meeting);
$note2 = $em->getEntityById('Note', $note2->getId());
$this->assertEquals([$team2->getId()], $note2->getLinkMultipleIdList('teams'));
$this->assertEquals([$user2->getId()], $note2->getLinkMultipleIdList('users'));
}
}

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\integration\Espo\ORM;
use Espo\Entities\Team;
use Espo\Modules\Crm\Entities\Account;
use tests\integration\Core\BaseTestCase;
class EntityManagerTest extends BaseTestCase
{
public function testRefreshEntity(): void
{
$em = $this->getEntityManager();
$team1 = $em->createEntity(Team::ENTITY_TYPE);
$team2 = $em->createEntity(Team::ENTITY_TYPE);
/** @var Account $account */
$account = $em->createEntity(Account::ENTITY_TYPE, [
'name' => 'Test',
'teamsIds' => [$team1->getId()],
]);
$account->set('name', 'Hello');
$em->getRelation($account, 'teams')->relateById($team2->getId());
$em->refreshEntity($account);
$this->assertEquals('Test', $account->get('name'));
$this->assertFalse($account->isAttributeChanged('name'));
$this->assertEqualsCanonicalizing([$team1->getId(), $team2->getId()], $account->getLinkMultipleIdList('teams'));
}
}

View File

@@ -0,0 +1,94 @@
<?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\integration\Espo\ORM;
use Espo\Core\Name\Field;
use Espo\Entities\Team;
use Espo\Modules\Crm\Entities\Account;
use tests\integration\Core\BaseTestCase;
class EntityTest extends BaseTestCase
{
public function testLinkMultiple(): void
{
$em = $this->getEntityManager();
$team1 = $em->createEntity(Team::ENTITY_TYPE);
$team2 = $em->createEntity(Team::ENTITY_TYPE);
$team3 = $em->createEntity(Team::ENTITY_TYPE);
$account = $em->createEntity(Account::ENTITY_TYPE, [
'teamsIds' => [
$team1->getId(),
$team2->getId(),
],
]);
$account = $em->getRDBRepositoryByClass(Account::class)->getById($account->getId());
$set = $account->getLinkMultipleIdList(Field::TEAMS);
$expected = [$team1->getId(), $team2->getId()];
$this->assertTrue(
array_diff($set, $expected) === array_diff($expected, $set)
);
//
$account = $em->getRDBRepositoryByClass(Account::class)->getById($account->getId());
$account->addLinkMultipleId(Field::TEAMS, $team3->getId());
$set = $account->getFetchedLinkMultipleIdList(Field::TEAMS);
$expected = [$team1->getId(), $team2->getId()];
$this->assertTrue(
array_diff($set, $expected) === array_diff($expected, $set)
);
$set = $account->getLinkMultipleIdList(Field::TEAMS);
$expected = [$team1->getId(), $team2->getId(), $team3->getId()];
$this->assertTrue(
array_diff($set, $expected) === array_diff($expected, $set)
);
//
$account = $em->getRDBRepositoryByClass(Account::class)->getById($account->getId());
$set = $account->getFetchedLinkMultipleIdList(Field::TEAMS);
$expected = [$team1->getId(), $team2->getId()];
$this->assertTrue(
array_diff($set, $expected) === array_diff($expected, $set)
);
}
}

View File

@@ -0,0 +1,51 @@
<?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\integration\Espo\ORM;
use Espo\ORM\Query\Part\Selection;
use tests\integration\Core\BaseTestCase;
class FunctionConverterTest extends BaseTestCase
{
public function testAbs(): void
{
$entityManager = $this->getEntityManager();
$query = $entityManager->getQueryBuilder()
->select(Selection::fromString('ABS:(-1)')->withAlias('value'))
->build();
$sth = $entityManager->getQueryExecutor()->execute($query);
$value = $sth->fetch()['value'];
$this->assertEquals('1', $value);
}
}

View File

@@ -0,0 +1,341 @@
<?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\integration\Espo\ORM;
use Espo\ORM\EntityManager;
use tests\integration\Core\BaseTestCase;
class MapperTest extends BaseTestCase
{
public function testRelate1()
{
$entityManager = $this->getContainer()->getByClass(EntityManager::class);
$account = $entityManager->getNewEntity('Account');
$account->set('name', 'Test');
$entityManager->saveEntity($account);
$contact = $entityManager->getNewEntity('Contact');
$contact->set('lastName', 'Test');
$entityManager->saveEntity($contact);
$entityManager->getRelation($account, 'contacts')->relate($contact);
$isRelated = $entityManager->getRelation($account, 'contacts')->isRelated($contact);
$this->assertTrue($isRelated);
$entityManager->getRelation($account, 'contacts')->unrelate($contact);
$isRelated = $entityManager->getRelation($account, 'contacts')->isRelated($contact);
$this->assertFalse($isRelated);
}
public function testRelate2()
{
$entityManager = $this->getEntityManager();
$account = $entityManager->getNewEntity('Account');
$account->set('name', 'Test');
$entityManager->saveEntity($account);
$contact = $entityManager->getNewEntity('Contact');
$contact->set('lastName', 'Test');
$entityManager->saveEntity($contact);
$entityManager->getRelation($contact, 'account')->relate($account);
$isRelated = $entityManager->getRelation($contact, 'account')->isRelated($account);
$this->assertTrue($isRelated);
$contact = $entityManager->getEntityById('Contact', $contact->getId());
$entityManager->getRelation($contact, 'account')->unrelate($account);
$isRelated = $entityManager->getRelation($contact, 'account')->isRelated($account);
$this->assertFalse($isRelated);
}
public function testRelate3WithEntityReFetching()
{
$entityManager = $this->getEntityManager();
$account = $entityManager->getNewEntity('Account');
$account->set('name', 'Test');
$entityManager->saveEntity($account);
$contact = $entityManager->getNewEntity('Contact');
$contact->set('lastName', 'Test');
$entityManager->saveEntity($contact);
$entityManager->getRDBRepository('Contact')
->getRelation($contact, 'account')
->relate($account);
$contact = $entityManager->getEntityById('Contact', $contact->get('id'));
$isRelated = $entityManager->getRDBRepository('Contact')
->getRelation($contact, 'account')
->isRelated($account);
$this->assertTrue($isRelated);
$entityManager = $this->getEntityManager();
$entityManager->getRDBRepository('Contact')
->getRelation($contact, 'account')
->unrelate($account);
$contact = $entityManager->getEntityById('Contact', $contact->get('id'));
$isRelated = $entityManager
->getRDBRepository('Contact')
->getRelation($contact, 'account')
->isRelated($account);
$this->assertFalse($isRelated);
}
public function testRelate4()
{
$entityManager = $this->getEntityManager();
$account = $entityManager->getNewEntity('Account');
$account->set('name', 'Test');
$entityManager->saveEntity($account);
$task = $entityManager->getNewEntity('Task');
$task->set('name', 'Test');
$entityManager->saveEntity($task);
$entityManager->getRelation($task, 'parent')->relate($account);
$isRelated = $entityManager->getRelation($task, 'parent')->isRelated($account);
$this->assertTrue($isRelated);
$task = $entityManager->getEntityById('Task', $task->getId());
$entityManager->getRelation($task, 'parent')->unrelate($account);
$isRelated = $entityManager->getRelation($task, 'parent')->isRelated($account);
$this->assertFalse($isRelated);
}
public function testRelateOneToOne1()
{
$em = $this->getEntityManager();
$a1 = $em->createEntity('Account', [
'name' => '1',
]);
$a2 = $em->createEntity('Account', [
'name' => '2',
]);
$l1 = $em->createEntity('Lead', [
'lastName' => '1',
]);
$em->createEntity('Lead', [
'lastName' => '2',
]);
$em->getRelation($l1, 'createdAccount')->relate($a1);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a1);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($a1, 'originalLead')->isRelated($l1);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a2);
$this->assertFalse($isRelated);
$isRelated = $em->getRelation($a2, 'originalLead')->isRelated($l1);
$this->assertFalse($isRelated);
$em->getRelation($l1, 'createdAccount')->relate($a2);
$isRelated = $em->getRelation($a2, 'originalLead')->isRelated($l1);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a2);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a1);
$this->assertFalse($isRelated);
$isRelated = $em->getRelation($a1, 'originalLead')->isRelated($l1);
$this->assertFalse($isRelated);
$c = $em->getRDBRepository('Lead')->where(['createdAccountId' => $a1->getId()])->count();
$this->assertEquals(0, $c);
$c = $em->getRDBRepository('Lead')->where(['createdAccountId' => $a2->getId()])->count();
$this->assertEquals(1, $c);
}
public function testRelateOneToOne2()
{
$em = $this->getEntityManager();
$a1 = $em->createEntity('Account', [
'name' => '1',
]);
$a2 = $em->createEntity('Account', [
'name' => '2',
]);
$l1 = $em->createEntity('Lead', [
'lastName' => '1',
]);
$em->getRelation($a1, 'originalLead')->relate($l1);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a1);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($a1, 'originalLead')->isRelated($l1);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a2);
$this->assertFalse($isRelated);
$isRelated = $em->getRelation($a2, 'originalLead')->isRelated($l1);
$this->assertFalse($isRelated);
$em->getRelation($a2, 'originalLead')->relate($l1);
$isRelated = $em->getRelation($a2, 'originalLead')->isRelated($l1);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a2);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a1);
$this->assertFalse($isRelated);
$isRelated = $em->getRelation($a1, 'originalLead')->isRelated($l1);
$this->assertFalse($isRelated);
$c = $em->getRDBRepository('Lead')
->where(['createdAccountId' => $a1->getId()])
->count();
$this->assertEquals(0, $c);
$c = $em->getRDBRepository('Lead')
->where(['createdAccountId' => $a2->getId()])
->count();
$this->assertEquals(1, $c);
}
public function testRelateOneToOne3()
{
$em = $this->getEntityManager();
$a1 = $em->createEntity('Account', [
'name' => '1',
]);
$em->createEntity('Account', [
'name' => '2',
]);
$l1 = $em->createEntity('Lead', [
'lastName' => '1',
]);
$l2 = $em->createEntity('Lead', [
'lastName' => '2',
]);
$em->getRelation($l1, 'createdAccount')->relate($a1);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a1);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($a1, 'originalLead')->isRelated($l1);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($l2, 'createdAccount')->isRelated($a1);
$this->assertFalse($isRelated);
$isRelated = $em->getRelation($a1, 'originalLead')->isRelated($l2);
$this->assertFalse($isRelated);
$em->getRelation($l2, 'createdAccount')->relate($a1);
$isRelated = $em->getRelation($a1, 'originalLead')->isRelated($l2);
$this->assertTrue($isRelated);
$isRelated = $em->getRelation($l2, 'createdAccount')->isRelated($a1);
$this->assertTrue($isRelated);
$l1 = $em->getEntityById('Lead', $l1->getId());
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a1);
$this->assertFalse($isRelated);
$isRelated = $em->getRelation($a1, 'originalLead')->isRelated($l1);
$this->assertFalse($isRelated);
$c = $em->getRDBRepository('Lead')
->where(['createdAccountId' => $a1->getId()])
->count();
$this->assertEquals(1, $c);
}
public function testUnrelateOneToOne1()
{
$em = $this->getEntityManager();
$a1 = $em->createEntity('Account', [
'name' => '1',
]);
$em->createEntity('Account', [
'name' => '2',
]);
$l1 = $em->createEntity('Lead', [
'lastName' => '1',
]);
$em->createEntity('Lead', [
'lastName' => '2',
]);
$em->getRelation($l1, 'createdAccount')->relate($a1);
$em->getRelation($l1, 'createdAccount')->unrelate($a1);
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a1);
$this->assertFalse($isRelated);
$em->getRelation($l1, 'createdAccount')->relate($a1);
$em->getRelation($a1, 'originalLead')->unrelate($l1);
$l1 = $em->getEntityById('Lead', $l1->getId());
$isRelated = $em->getRelation($l1, 'createdAccount')->isRelated($a1);
$this->assertFalse($isRelated);
}
}

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\integration\Espo\ORM;
use PDO;
use tests\integration\Core\BaseTestCase;
class QueryTest extends BaseTestCase
{
public function testQuery1()
{
$entityManager = $this->getEntityManager();
$account = $entityManager->getNewEntity('Account');
$account->set('name', 'Test');
$entityManager->saveEntity($account);
$query = $entityManager->getQueryBuilder()
->select()
->from('Account')
->select('id')
->select("CONCAT:(',test',\"+\",'\"', \"',ROUND(1)\")", 'value')
->build();
$rowList = [];
$sth = $entityManager->getQueryExecutor()->execute($query);
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
$rowList[] = $row;
}
$this->assertEquals(",test+\"',ROUND(1)", $rowList[0]['value']);
}
}

View File

@@ -0,0 +1,311 @@
<?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\integration\Espo\ORM;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Modules\Crm\Entities\Task;
use Espo\ORM\Defs\Params\RelationParam;
use Espo\ORM\EntityCollection;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Opportunity;
use Espo\ORM\Repository\Option\SaveOption;
use tests\integration\Core\BaseTestCase;
use tests\integration\testClasses\Entities\Account as AccountExtended;
class RelationsTest extends BaseTestCase
{
public function testGetOne(): void
{
$metadata = $this->getMetadata();
$metadata->set('entityDefs', Opportunity::ENTITY_TYPE, [
'links' => [
'account' => [
'deferredLoad' => true,
],
],
]);
$metadata->save();
$this->authenticate();
$em = $this->getEntityManager();
$opp = $em->getNewEntity(Opportunity::ENTITY_TYPE);
$this->assertInstanceOf(Opportunity::class, $opp);
$this->assertNull($opp->getAccount());
/** @var Account $account */
$account = $em->createEntity(Account::ENTITY_TYPE, ['name' => 'Account 1']);
$opp = $em->createEntity(Opportunity::ENTITY_TYPE, [
'accountId' => $account->getId(),
]);
$this->assertInstanceOf(Opportunity::class, $opp);
$account1 = $opp->getAccount();
$this->assertInstanceOf(Account::class, $account1);
$this->assertEquals($account->getId(), $account1->getId());
$em->refreshEntity($opp);
$account2 = $opp->getAccount();
$this->assertInstanceOf(Account::class, $account2);
$this->assertNotSame($account1, $account2);
$this->assertEquals($account->getId(), $account2->getId());
$this->assertEquals($account->getName(), $account2->getName());
$em->saveEntity($opp);
$account3 = $opp->getAccount();
$this->assertInstanceOf(Account::class, $account3);
$this->assertNotSame($account1, $account3);
$this->assertEquals($account->getId(), $account3->getId());
// Soft-deleted.
$em->removeEntity($account3);
$em->refreshEntity($opp);
$this->assertNull($opp->getAccount());
}
public function testGetMany(): void
{
$metadata = $this->getMetadata();
$metadata->set('entityDefs', Account::ENTITY_TYPE, [
'entityClassName' => AccountExtended::class,
]);
$metadata->save();
$this->authenticate();
$em = $this->getEntityManager();
$account = $em->getNewEntity(Account::ENTITY_TYPE);
$this->assertInstanceOf(AccountExtended::class, $account);
$this->assertInstanceOf(EntityCollection::class, $account->getRelatedOpportunities());
$account = $em->createEntity(Account::ENTITY_TYPE);
$this->assertInstanceOf(AccountExtended::class, $account);
$em->createEntity(Opportunity::ENTITY_TYPE, ['accountId' => $account->getId()]);
$em->createEntity(Opportunity::ENTITY_TYPE, ['accountId' => $account->getId()]);
$em->createEntity(Opportunity::ENTITY_TYPE);
$collection = $account->getRelatedOpportunities();
$this->assertInstanceOf(EntityCollection::class, $collection);
$items = iterator_to_array($collection);
$this->assertCount(2, $items);
}
public function testSet(): void
{
$metadata = $this->getMetadata();
$metadata->set('entityDefs', Account::ENTITY_TYPE, [
'entityClassName' => AccountExtended::class,
]);
$metadata->save();
$this->authenticate(null);
//$this->reCreateApplication();
$em = $this->getEntityManager();
/** @var Account $account */
$account = $em->createEntity(Account::ENTITY_TYPE, ['name' => 'Account 1']);
// belongsTo
$opp = $em->getRDBRepositoryByClass(Opportunity::class)->getNew();
$opp->setAccount($account);
$em->saveEntity($opp, [SaveOption::SKIP_ALL => true]);
$em->refreshEntity($opp);
$this->assertEquals($account->getId(), $opp->getAccount()->getId());
$this->assertEquals($account->getName(), $opp->getAccount()->getName());
$opp->setAccount(null);
$em->saveEntity($opp, [SaveOption::SKIP_ALL => true]);
$em->refreshEntity($opp);
$this->assertNull($opp->getAccount());
// belongsToParent
$task = $em->getRDBRepositoryByClass(Task::class)->getNew();
$task->setParent($account);
$em->saveEntity($task, [SaveOption::SKIP_ALL => true]);
$em->refreshEntity($task);
$this->assertEquals($account->getId(), $task->get('parentId'));
$this->assertEquals($account->getEntityType(), $task->get('parentType'));
$this->assertEquals($account->getId(), $task->getParent()->getId());
$this->assertEquals($account->getName(), $task->getParent()->get('name'));
$task = $em->getRDBRepositoryByClass(Task::class)->getNew();
$task->setParent(null);
$em->saveEntity($task, [SaveOption::SKIP_ALL => true]);
$em->refreshEntity($task);
$this->assertNull($task->get('parentId'));
$this->assertNull($task->get('parentType'));
// belongsTo hasOne
$lead1 = $em->getRDBRepositoryByClass(Lead::class)->getNew();
$lead1->setCreatedAccount($account);
$em->saveEntity($lead1, [SaveOption::SKIP_ALL => true]);
$em->refreshEntity($lead1);
$this->assertEquals($account->getId(), $lead1->get('createdAccountId'));
$lead2 = $em->getRDBRepositoryByClass(Lead::class)->getNew();
$lead2->setCreatedAccount($account);
$em->saveEntity($lead2, [SaveOption::SKIP_ALL => true]);
$em->refreshEntity($lead2);
$this->assertEquals($account->getId(), $lead2->get('createdAccountId'));
$em->refreshEntity($lead1);
$this->assertEquals(null, $lead1->get('createdAccountId'));
$lead2->setCreatedAccount(null);
$em->saveEntity($lead2);
// hasOne
$em->refreshEntity($account);
$this->assertInstanceOf(AccountExtended::class, $account);
$account->setRelatedOriginalLead($lead1);
$em->saveEntity($account, [SaveOption::SKIP_ALL => true]);
$em->refreshEntity($lead1);
$this->assertEquals($account->getId(), $lead1->get('createdAccountId'));
$account->setRelatedOriginalLead($lead2);
$em->saveEntity($account, [SaveOption::SKIP_ALL => true]);
$em->refreshEntity($lead2);
$this->assertEquals($account->getId(), $lead2->get('createdAccountId'));
$em->refreshEntity($lead1);
$this->assertEquals(null, $lead1->get('createdAccountId'));
$account->setRelatedOriginalLead(null);
$em->saveEntity($account, [SaveOption::SKIP_ALL => true]);
$em->refreshEntity($lead2);
$this->assertEquals(null, $lead2->get('createdAccountId'));
}
public function testOrder(): void
{
$em = $this->getEntityManager();
$account1 = $em->createEntity(Account::ENTITY_TYPE, ['name' => 'a1']);
$account2 = $em->createEntity(Account::ENTITY_TYPE, ['name' => 'a2']);
$contact = $em->createEntity(Contact::ENTITY_TYPE);
$this->assertInstanceOf(Contact::class, $contact);
$em->getRelation($contact, 'accounts')->relate($account1);
$em->getRelation($contact, 'accounts')->relate($account2);
//
$metadata = $this->getMetadata();
$metadata->set('entityDefs', Contact::ENTITY_TYPE, [
'links' => [
'accounts' => [
RelationParam::ORDER_BY => 'name',
RelationParam::ORDER => 'desc',
]
]
]);
$metadata->save();
$this->getDataManager()->rebuildMetadata();
$this->reCreateApplication(reuse: true);
$em = $this->getEntityManager();
$contact = $em->getRDBRepositoryByClass(Contact::class)->getById($contact->getId());
$this->assertNotNull($contact);
$accounts = $contact->getAccounts();
$this->assertCount(2, $accounts);
$this->assertEquals('a2', $accounts[0]->getName());
$this->assertEquals('a1', $accounts[1]->getName());
//
$metadata = $this->getMetadata();
$metadata->set('entityDefs', Contact::ENTITY_TYPE, [
'links' => [
'accounts' => [
RelationParam::ORDER_BY => 'name',
RelationParam::ORDER => 'asc',
]
]
]);
$metadata->save();
$this->getDataManager()->clearCache();
$this->getDataManager()->rebuildMetadata();
$this->reCreateApplication(reuse: true);
$em = $this->getEntityManager();
$contact = $em->getRDBRepositoryByClass(Contact::class)->getById($contact->getId());
$this->assertNotNull($contact);
$accounts = $contact->getAccounts();
$this->assertCount(2, $accounts);
$this->assertEquals('a1', $accounts[0]->getName());
$this->assertEquals('a2', $accounts[1]->getName());
}
}

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\integration\Espo\ORM;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Opportunity;
use tests\integration\Core\BaseTestCase;
class RepositoryTest extends BaseTestCase
{
public function testModifiedBy(): void
{
$user1 = $this->createUser('test-1');
$user2 = $this->createUser('test-2');
$this->authenticate('test-1');
$em = $this->getEntityManager();
$account = $em->createEntity(Account::ENTITY_TYPE, ['name' => '1']);
$this->assertEquals($user1->getId(), $account->get('createdById'));
$this->authenticate('test-2');
$em = $this->getEntityManager();
$account = $em->getEntityById(Account::ENTITY_TYPE, $account->getId());
$account->set('name', '2');
$em->saveEntity($account);
$this->assertEquals($user2->getId(), $account->get('modifiedById'));
$this->authenticate('test-1');
$em = $this->getEntityManager();
$account = $em->getEntityById(Account::ENTITY_TYPE, $account->getId());
$account->set('name', '2');
$em->saveEntity($account);
$this->assertEquals($user2->getId(), $account->get('modifiedById'));
}
public function testIsRelated(): void
{
$em = $this->getEntityManager();
$acc1 = $em->createEntity(Account::ENTITY_TYPE, []);
$acc2 = $em->createEntity(Account::ENTITY_TYPE, []);
$opp1 = $em->createEntity(Opportunity::ENTITY_TYPE, []);
$contact1 = $em->createEntity(Contact::ENTITY_TYPE, []);
$contact2 = $em->createEntity(Contact::ENTITY_TYPE, []);
$oppRepo = $em->getRDBRepositoryByClass(Opportunity::class);
$oppRepo->getRelation($opp1, 'account')->relate($acc1);
$oppRepo->getRelation($opp1, 'contacts')->relate($contact1);
$this->assertTrue(
$oppRepo->getRelation($opp1, 'account')->isRelatedById($acc1->getId())
);
$this->assertTrue(
$oppRepo->getRelation($opp1, 'account')->isRelated($acc1)
);
$this->assertFalse(
$oppRepo->getRelation($opp1, 'account')->isRelatedById($acc2->getId())
);
$this->assertFalse(
$oppRepo->getRelation($opp1, 'account')->isRelated($acc2)
);
$this->assertTrue(
$oppRepo->getRelation($opp1, 'contacts')->isRelatedById($contact1->getId())
);
$this->assertTrue(
$oppRepo->getRelation($opp1, 'contacts')->isRelated($contact1)
);
$this->assertFalse(
$oppRepo->getRelation($opp1, 'contacts')->isRelatedById($contact2->getId())
);
$this->assertFalse(
$oppRepo->getRelation($opp1, 'contacts')->isRelated($contact2)
);
}
}

View File

@@ -0,0 +1,245 @@
<?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\integration\Espo\ORM;
use Espo\Modules\Crm\Entities\Account;
use Espo\ORM\SthCollection;
use tests\integration\Core\BaseTestCase;
class SthCollectionTest extends BaseTestCase
{
public function test1()
{
$em = $this->getEntityManager();
$em->createEntity('Account', [
'name' => 'test-1',
]);
$em->createEntity('Account', [
'name' => 'test-2',
]);
$em->createEntity('Account', [
'name' => 'test-3',
]);
$query = $em->getQueryBuilder()
->select()
->from('Account')
->limit(0, 2)
->order('name')
->build();
$collection = $em->getCollectionFactory()->createFromQuery($query);
$count = 0;
$list = [];
foreach ($collection as $e) {
$count++;
$list[] = $e;
}
$this->assertEquals(2, $count);
$this->assertEquals('test-1', $list[0]->get('name'));
$array = $collection->getValueMapList();
$this->assertEquals('test-2', $array[1]->name);
}
public function test2(): void
{
$em = $this->getEntityManager();
$em->createEntity('Account', [
'name' => 'test-2',
]);
$em->createEntity('Account', [
'name' => 'test-3',
]);
$query = $em->getQueryBuilder()
->select()
->from('Account')
->limit(0, 2)
->order('name')
->build();
$collection = $em->getCollectionFactory()->createFromQuery($query);
$count = 0;
foreach ($collection as $ignored) {
$count++;
}
$this->assertEquals(2, $count);
}
public function testFind1()
{
$em = $this->getEntityManager();
$em->createEntity('Account', [
'name' => 'test-1',
]);
$em->createEntity('Account', [
'name' => 'test-2',
]);
$em->createEntity('Account', [
'name' => 'test-3',
]);
$query = $em->getQueryBuilder()
->select()
->from('Account')
->where(['name' => 'test-1'])
->build();
$collection = $em->getRDBRepository('Account')
->clone($query)
->sth()
->find();
$this->assertEquals(SthCollection::class, get_class($collection));
$count = 0;
foreach ($collection as $e) {
$count++;
}
$this->assertEquals(1, $count);
}
public function testFindRelatedOneToMany()
{
$em = $this->getEntityManager();
$account = $em->createEntity('Account', [
'name' => 'test-1',
]);
$em->createEntity('Opportunity', [
'name' => 'o-1',
'accountId' => $account->getId(),
]);
$em->createEntity('Opportunity', [
'name' => 'o-2',
'accountId' => $account->getId(),
]);
$query = $em->getQueryBuilder()
->select()
->from('Opportunity')
->order('name')
->build();
$collection = $em
->getRelation($account, 'opportunities')
->clone($query)
->sth()
->find();
$this->assertEquals(SthCollection::class, get_class($collection));
$count = 0;
foreach ($collection as $e) {
$count++;
}
$this->assertEquals(2, $count);
$array = $collection->getValueMapList();
$this->assertEquals('o-1', $array[0]->name);
}
public function testFindRelatedManyToMany()
{
$em = $this->getEntityManager();
$contact = $em->createEntity('Contact', [
'lastName' => 'test-1',
]);
$em->createEntity('Opportunity', [
'name' => 'o-1',
'contactsIds' => [$contact->getId()],
]);
$em->createEntity('Opportunity', [
'name' => 'o-2',
'contactsIds' => [$contact->getId()],
]);
$query = $em->getQueryBuilder()
->select()
->from('Opportunity')
->build();
$collection = $em->getRepository('Contact')->getRelation($contact, 'opportunities')
->clone($query)
->sth()
->find();
$this->assertEquals(SthCollection::class, get_class($collection));
$count = 0;
foreach ($collection as $e) {
$count++;
}
$this->assertEquals(2, $count);
}
public function testMethods(): void
{
$e1 = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, ['name' => '1']);
$e2 = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, ['name' => '2']);
$e3 = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, ['name' => '3']);
$e4 = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, ['name' => '4']);
$collection = $this->getEntityManager()
->getRDBRepositoryByClass(Account::class)
->sth()
->order('name')
->find();
$filtered = $collection->filter(function ($e) use ($e2, $e3) {
return $e->getId() !== $e2->getId() && $e->getId() !== $e3->getId();
});
$this->assertEquals([$e1->getId(), $e4->getId()], array_map(fn ($it) => $it->getId(), [...$filtered]));
$this->assertEquals($collection->getEntityType(), $filtered->getEntityType());
}
}

View File

@@ -0,0 +1,170 @@
<?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\integration\Espo\ORM;
use tests\integration\Core\BaseTestCase;
class TransactionManagerTest extends BaseTestCase
{
public function testOne()
{
$em = $this->getEntityManager();
$tm = $em->getTransactionManager();
$tm->start();
$account = $em->createEntity('Account', [
'name' => 'test',
]);
$this->assertNotNull($account);
$id = $account->getId();
$tm->commit();
$account = $em->getEntityById('Account', $id);
$this->assertNotNull($account);
}
public function testRollbackOne()
{
$em = $this->getEntityManager();
$tm = $em->getTransactionManager();
$tm->start();
$account = $em->createEntity('Account', [
'name' => 'test',
]);
$this->assertNotNull($account);
$id = $account->getId();
$tm->rollback();
$account = $em->getEntityById('Account', $id);
$this->assertNull($account);
}
public function testRollbackNested()
{
$em = $this->getEntityManager();
$tm = $em->getTransactionManager();
$tm->start();
$account1 = $em->createEntity('Account', [
'name' => 'test1',
]);
$id1 = $account1->getId();
$tm->start();
$account2 = $em->createEntity('Account', [
'name' => 'test2',
]);
$id2 = $account2->getId();
$tm->rollback();
$tm->commit();
$account1 = $em->getEntityById('Account', $id1);
$account2 = $em->getEntityById('Account', $id2);
$this->assertNotNull($account1);
$this->assertNull($account2);
}
public function testRunCommit()
{
$em = $this->getEntityManager();
$tm = $em->getTransactionManager();
$account = $em->createEntity('Account', [
'name' => 'test',
]);
$id = $account->getId();
$tm->run(
function () use ($em, $id){
$account = $em->getEntityById('Account', $id);
$account->set('name', 'test-1');
$em->saveEntity($account);
}
);
$account = $em->getEntityById('Account', $id);
$this->assertNotNull($account);
$this->assertEquals('test-1', $account->get('name'));
}
public function testRunRollback()
{
$em = $this->getEntityManager();
$tm = $em->getTransactionManager();
$account = $em->createEntity('Account', [
'name' => 'test',
]);
$id = $account->getId();
try {
$tm->run(
function () use ($em, $id){
$account = $em->getEntity('Account', $id);
$account->set('name', 'test-1');
$em->saveEntity($account);
throw new \Exception();
}
);
} catch (\Exception) {}
$account = $em->getEntityById('Account', $id);
$this->assertNotNull($account);
$this->assertEquals('test', $account->get('name'));
}
}

View File

@@ -0,0 +1,116 @@
<?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\integration\Espo\Password;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Utils\Config\ConfigWriter;
use Espo\Entities\Portal;
use Espo\Tools\UserSecurity\Password\Recovery\UrlValidator;
use tests\integration\Core\BaseTestCase;
class RecoveryTest extends BaseTestCase
{
private ?string $storedSiteUrl = null;
private string $siteUrl = 'https://my-site.com/';
protected function setUp(): void
{
parent::setUp();
$writer = $this->getInjectableFactory()->create(ConfigWriter::class);
$writer->set('siteUrl', $this->siteUrl);
$writer->save();
$this->storedSiteUrl = $this->getConfig()->get('siteUrl');
}
protected function tearDown(): void
{
$writer = $this->getInjectableFactory()->create(ConfigWriter::class);
$writer->set('siteUrl', $this->storedSiteUrl);
$writer->save();
$this->storedSiteUrl = null;
parent::tearDown();
}
public function testUrlValidation()
{
$em = $this->getEntityManager();
$em->createEntity(Portal::ENTITY_TYPE, [
'customUrl' => 'https://my-portal.com/',
]);
/** @var Portal $portal2 */
$portal2 = $em->createEntity(Portal::ENTITY_TYPE, [
'isDefault' => true,
]);
$validator = $this->getInjectableFactory()->create(UrlValidator::class);
/** @noinspection PhpUnhandledExceptionInspection */
$validator->validate('https://my-site.com');
/** @noinspection PhpUnhandledExceptionInspection */
$validator->validate('https://my-site.com/');
/** @noinspection PhpUnhandledExceptionInspection */
$validator->validate('https://my-site.com#Test');
/** @noinspection PhpUnhandledExceptionInspection */
$validator->validate('https://my-site.com/portal');
/** @noinspection PhpUnhandledExceptionInspection */
$validator->validate('https://my-portal.com');
/** @noinspection PhpUnhandledExceptionInspection */
$validator->validate('https://my-portal.com/');
/** @noinspection PhpUnhandledExceptionInspection */
$validator->validate('https://my-portal.com/#Test');
/** @noinspection PhpUnhandledExceptionInspection */
$validator->validate('https://my-site.com/portal/' . $portal2->getId());
$thrown = false;
try {
$validator->validate('https://not-my-site.com');
}
catch (Forbidden) {
$thrown = true;
}
$this->assertTrue($thrown);
}
}

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\integration\Espo\Password;
use Espo\Entities\User;
use tests\integration\Core\BaseTestCase;
class VersionTest extends BaseTestCase
{
public function testVersionIncrement(): void
{
$em = $this->getEntityManager();
$user = $em->getRDBRepositoryByClass(User::class)->getNew();
$user->setUserName('test');
$em->saveEntity($user);
$version = $user->get(User::FIELD_PASSWORD_VERSION);
$user->set(User::FIELD_PASSWORD, '1');
$em->saveEntity($user);
$this->assertEquals($version + 1, $user->get(User::FIELD_PASSWORD_VERSION));
}
}

View File

@@ -0,0 +1,372 @@
<?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\integration\Espo\Portal;
use Espo\Core\Acl;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Select\SearchParams;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\CaseObj;
use tests\integration\Core\BaseTestCase;
class AclTest extends BaseTestCase
{
public function testAccessContact(): void
{
$em = $this->getEntityManager();
$contact = $em->createEntity('Contact', []);
$portal = $em->createEntity('Portal', [
'name' => 'Portal',
]);
$this->createUser([
'userName' => 'tester',
'portalsIds' => [$portal->getId()],
'contactId' => $contact->getId(),
], [
'data' => [
'Case' => [
'create' => 'no',
'read' => 'contact',
'edit' => 'no',
'delete' => 'no',
'stream' => 'contact',
]
],
], true);
$this->auth(
userName: 'tester',
portalId: $portal->getId(),
);
$app = $this->createApplication(
portalId: $portal->getId(),
reuse: true
);
$this->setApplication($app);
$em = $this->getEntityManager();
$acl = $this->getContainer()->getByClass(Acl::class);
$case1 = $em->createEntity('Case', [
'contactId' => $contact->getId(),
], ['createdById' => '1']);
$case2 = $em->createEntity('Case', [
'contactsIds' => [$contact->getId()],
], ['createdById' => '1']);
$case3 = $em->createEntity('Case', [
], ['createdById' => '1']);
$case4 = $em->createEntity('Case', [
'contactsIds' => [$contact->getId()],
'isInternal' => true,
], ['createdById' => '1']);
$this->assertFalse($acl->check('Case', 'create'));
$this->assertFalse($acl->check('Case', 'edit'));
$this->assertFalse($acl->check('Case', 'delete'));
$this->assertTrue($acl->check($case1, 'read'));
$this->assertTrue($acl->check($case2, 'read'));
$this->assertFalse($acl->check($case3, 'read'));
$this->assertFalse($acl->check($case4, 'read'));
$service = $app->getContainer()->getByClass(ServiceContainer::class)->get('Case');
$result = $service->find(SearchParams::create());
$idList = [];
foreach ($result->getCollection() as $e) {
$idList[] = $e->getId();
}
$this->assertTrue(in_array($case1->getId(), $idList));
$this->assertTrue(in_array($case2->getId(), $idList));
$this->assertFalse(in_array($case3->getId(), $idList));
$this->assertFalse(in_array($case4->getId(), $idList));
}
public function testAccessAccount(): void
{
$em = $this->getEntityManager();
$contact = $em->createEntity('Contact', []);
$account = $em->createEntity('Account', []);
$portal = $em->createEntity('Portal', [
'name' => 'Portal',
]);
$this->createUser([
'userName' => 'tester',
'portalsIds' => [$portal->getId()],
'contactId' => $contact->getId(),
'accountsIds' => [$account->getId()],
], [
'data' => [
'Case' => [
'create' => 'no',
'read' => 'account',
'edit' => 'no',
'delete' => 'no',
'stream' => 'account',
]
],
], true);
$this->auth(
userName: 'tester',
portalId: $portal->getId(),
);
$app = $this->createApplication(
portalId: $portal->getId(),
reuse: true
);
$this->setApplication($app);
$em = $this->getEntityManager();
$acl = $this->getContainer()->getByClass(Acl::class);
$case1 = $em->createEntity('Case', [
'contactId' => $contact->getId(),
], ['createdById' => '1']);
$case2 = $em->createEntity('Case', [
'contactsIds' => [$contact->getId()],
], ['createdById' => '1']);
$case3 = $em->createEntity('Case', [
], ['createdById' => '1']);
$case4 = $em->createEntity('Case', [
'accountId' => $account->getId(),
], ['createdById' => '1']);
$case5 = $em->createEntity('Case', [
'accountId' => $account->getId(),
'isInternal' => true,
], ['createdById' => '1']);
$this->assertFalse($acl->check('Case', 'create'));
$this->assertFalse($acl->check('Case', 'edit'));
$this->assertFalse($acl->check('Case', 'delete'));
$this->assertTrue($acl->check($case1, 'read'));
$this->assertTrue($acl->check($case2, 'read'));
$this->assertFalse($acl->check($case3, 'read'));
$this->assertTrue($acl->check($case4, 'read'));
$this->assertFalse($acl->check($case5, 'read'));
$service = $app->getContainer()->getByClass(ServiceContainer::class)->get('Case');
$result = $service->find(SearchParams::create());
$idList = [];
foreach ($result->getCollection() as $e) {
$idList[] = $e->getId();
}
$this->assertTrue(in_array($case1->getId(), $idList));
$this->assertTrue(in_array($case2->getId(), $idList));
$this->assertFalse(in_array($case3->getId(), $idList));
$this->assertTrue(in_array($case4->getId(), $idList));
$this->assertFalse(in_array($case5->getId(), $idList));
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testAccessOwn(): void
{
$em = $this->getEntityManager();
$contact = $em->createEntity('Contact', []);
$account = $em->createEntity('Account', []);
$portal = $em->createEntity('Portal', [
'name' => 'Portal',
]);
$this->createUser([
'userName' => 'tester',
'portalsIds' => [$portal->getId()],
'contactId' => $contact->getId(),
'accountsIds' => [$account->getId()],
], [
'data' => [
'Case' => [
'create' => 'no',
'read' => 'own',
'edit' => 'no',
'delete' => 'no',
'stream' => 'own',
]
],
], true);
$this->auth(
userName: 'tester',
portalId: $portal->getId(),
);
$app = $this->createApplication(
portalId: $portal->getId(),
reuse: true
);
$this->setApplication($app);
$em = $this->getEntityManager();
$acl = $this->getContainer()->getByClass(Acl::class);
$user = $this->getContainer()->getByClass(User::class);
$case1 = $em->createEntity('Case', [
'contactId' => $contact->getId(),
], ['createdById' => '1']);
$case2 = $em->createEntity('Case', [
'contactsIds' => [$contact->getId()],
], ['createdById' => '1']);
$case3 = $em->createEntity('Case', [
], ['createdById' => '1']);
$case4 = $em->createEntity('Case', [
'accountId' => $account->getId(),
], ['createdById' => '1']);
$case5 = $em->createEntity('Case', [
'accountId' => $account->getId(),
], ['createdById' => $user->getId()]);
$case6 = $em->createEntity('Case', [
'accountId' => $account->getId(),
'isInternal' => true,
], ['createdById' => '1']);
$this->assertFalse($acl->check('Case', 'create'));
$this->assertFalse($acl->check('Case', 'edit'));
$this->assertFalse($acl->check('Case', 'delete'));
$this->assertFalse($acl->check($case1, 'read'));
$this->assertFalse($acl->check($case2, 'read'));
$this->assertFalse($acl->check($case3, 'read'));
$this->assertFalse($acl->check($case4, 'read'));
$this->assertTrue($acl->check($case5, 'read'));
$this->assertFalse($acl->check($case6, 'read'));
$service = $this->getContainer()->getByClass(ServiceContainer::class)->get('Case');
$result = $service->find(SearchParams::create());
$idList = [];
foreach ($result->getCollection() as $e) {
$idList[] = $e->getId();
}
$this->assertFalse(in_array($case1->getId(), $idList));
$this->assertFalse(in_array($case2->getId(), $idList));
$this->assertFalse(in_array($case3->getId(), $idList));
$this->assertFalse(in_array($case4->getId(), $idList));
$this->assertTrue(in_array($case5->getId(), $idList));
$this->assertFalse(in_array($case6->getId(), $idList));
}
public function testCreateCase(): void
{
$em = $this->getEntityManager();
$contact = $em->createEntity('Contact');
$account = $em->createEntity('Account');
$contactNotOwn = $em->createEntity('Contact');
$accountNotOwn = $em->createEntity('Account');
$portal = $em->createEntity('Portal', [
'name' => 'Portal',
]);
$this->createUser([
'userName' => 'tester',
'portalsIds' => [$portal->getId()],
'contactId' => $contact->getId(),
'accountsIds' => [$account->getId()],
], [
'data' => [
'Case' => [
'create' => 'yes',
'read' => 'own',
'edit' => 'no',
'delete' => 'no',
'stream' => 'own',
]
],
], true);
$this->auth(
userName: 'tester',
portalId: $portal->getId(),
);
$app = $this->createApplication(
portalId: $portal->getId(),
reuse: true,
);
$this->setApplication($app);
$caseService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(CaseObj::class);
/** @noinspection PhpUnhandledExceptionInspection */
$caseService->create((object) [
'name' => 'Test 1',
'accountId' => $account->getId(),
'contactId' => $contact->getId(),
'contactsIds' => [$contact->getId()],
], CreateParams::create());
$isThrown = false;
try {
/** @noinspection PhpUnhandledExceptionInspection */
$caseService->create((object) [
'name' => 'Test 1',
'accountId' => $accountNotOwn->getId(),
'contactId' => $contactNotOwn->getId(),
'contactsIds' => [$contactNotOwn->getId()],
], CreateParams::create());
} catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
}
}

View File

@@ -0,0 +1,130 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Record;
use Espo\Core\Acl\Table;
use Espo\Core\Record\ServiceContainer;
use Espo\Entities\Team;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Lead;
use tests\integration\Core\BaseTestCase;
class AssignmentTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testAssignmentSelf(): void
{
$team1 = $this->getEntityManager()->createEntity(Team::ENTITY_TYPE);
$user1 = $this->createUser([
'userName' => 'test1',
'defaultTeamId' => $team1->getId(),
'teamsIds' => [$team1->getId()],
], [
'data' => [
Lead::ENTITY_TYPE => [
'create' => Table::LEVEL_YES,
'read' => Table::LEVEL_OWN,
'edit' => Table::LEVEL_OWN,
'delete' => Table::LEVEL_OWN,
],
],
'assignmentPermission' => Table::LEVEL_NO,
]);
$this->authenticate('test1');
$lead1 = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Lead::class)
->create((object) [
'lastName' => 'Test 1',
])->getEntity();
$this->assertEquals($user1->getId(), $lead1->getAssignedUser()?->getId());
$this->assertEquals([$team1->getId()], $lead1->getLinkMultipleIdList('teams'));
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testAssignment1(): void
{
$team = $this->getEntityManager()->createEntity(Team::ENTITY_TYPE);
$this->createUser([
'userName' => 'test1',
'defaultTeamId' => $team->getId(),
'teamsIds' => [$team->getId()],
], [
'data' => [
Lead::ENTITY_TYPE => [
'create' => Table::LEVEL_YES,
'read' => Table::LEVEL_OWN,
'edit' => Table::LEVEL_OWN,
'delete' => Table::LEVEL_OWN,
],
User::ENTITY_TYPE => [
'read' => Table::LEVEL_TEAM,
],
],
'assignmentPermission' => Table::LEVEL_TEAM,
]);
$user2 = $this->createUser([
'userName' => 'test2',
'defaultTeamId' => $team->getId(),
'teamsIds' => [$team->getId()],
], [
'data' => [
Lead::ENTITY_TYPE => [
'create' => Table::LEVEL_YES,
'read' => Table::LEVEL_OWN,
'edit' => Table::LEVEL_OWN,
'delete' => Table::LEVEL_OWN,
],
],
]);
$this->authenticate('test1');
$lead = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Lead::class)
->create((object) [
'lastName' => 'Test 1',
'assignedUserId' => $user2->getId(),
])->getEntity();
$this->assertEquals($user2->getId(), $lead->getAssignedUser()?->getId());
}
}

View File

@@ -0,0 +1,200 @@
<?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\integration\Espo\Record;
use Espo\Core\Record\ServiceContainer;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Opportunity;
use Espo\Modules\Crm\Entities\Task;
use Espo\ORM\Defs\Params\RelationParam;
use Espo\ORM\Name\Attribute;
use Espo\ORM\Query\SelectBuilder;
use tests\integration\Core\BaseTestCase;
class CascadeRemovalTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testRemovalAndRestoral(): void
{
$metadata = $this->getMetadata();
$metadata->set('entityDefs', 'Account', [
'links' => [
'opportunities' => [
RelationParam::CASCADE_REMOVAL => true,
]
]
]);
$metadata->set('entityDefs', 'Opportunity', [
'links' => [
'tasks' => [
RelationParam::CASCADE_REMOVAL => true,
]
]
]);
$metadata->save();
$this->getDataManager()->clearCache();
$this->getDataManager()->rebuildMetadata();
$this->reCreateApplication(reuse: true);
$em = $this->getEntityManager();
$account = $em->createEntity('Account', ['name' => 'a1']);
$contact = $em->createEntity('Contact', ['lastName' => 'c1', 'accountId' => $account->getId()]);
$opp1 = $em->createEntity('Opportunity', ['name' => 'o1', 'accountId' => $account->getId()]);
$opp2 = $em->createEntity('Opportunity', ['name' => 'o2', 'accountId' => $account->getId()]);
$opp3 = $em->createEntity('Opportunity', ['name' => 'o3']);
$task1 = $em->createEntity('Task', [
'name' => 't1',
'parentType' => 'Opportunity',
'parentId' => $opp1->getId(),
]);
$em->removeEntity($account);
$this->assertNotNull(
$em->getRDBRepository(Account::ENTITY_TYPE)
->clone(
SelectBuilder::create()
->from(Account::ENTITY_TYPE)
->withDeleted()
->build()
)
->where([
Attribute::ID => $account->getId(),
Attribute::DELETED => true,
])
->findOne()
);
$this->assertNotNull(
$em->getRDBRepository(Contact::ENTITY_TYPE)
->where([
Attribute::ID => $contact->getId(),
])
->findOne()
);
$this->assertNotNull(
$em->getRDBRepository(Opportunity::ENTITY_TYPE)
->clone(
SelectBuilder::create()
->from(Opportunity::ENTITY_TYPE)
->withDeleted()
->build()
)
->where([
Attribute::ID => $opp1->getId(),
Attribute::DELETED => true,
])
->findOne()
);
$this->assertNotNull(
$em->getRDBRepository(Opportunity::ENTITY_TYPE)
->clone(
SelectBuilder::create()
->from(Opportunity::ENTITY_TYPE)
->withDeleted()
->build()
)
->where([
Attribute::ID => $opp2->getId(),
Attribute::DELETED => true,
])
->findOne()
);
$this->assertNotNull(
$em->getRDBRepository(Task::ENTITY_TYPE)
->clone(
SelectBuilder::create()
->from(Task::ENTITY_TYPE)
->withDeleted()
->build()
)
->where([
Attribute::ID => $task1->getId(),
Attribute::DELETED => true,
])
->findOne()
);
$this->assertNotNull(
$em->getRDBRepository(Opportunity::ENTITY_TYPE)
->where([
Attribute::ID => $opp3->getId(),
])
->findOne()
);
//
$service = $this->getContainer()->getByClass(ServiceContainer::class)->get(Account::ENTITY_TYPE);
$service->restoreDeleted($account->getId());
$this->assertNotNull(
$em->getRDBRepository(Account::ENTITY_TYPE)
->where([
Attribute::ID => $account->getId(),
])
->findOne()
);
$this->assertNotNull(
$em->getRDBRepository(Opportunity::ENTITY_TYPE)
->where([
Attribute::ID => $opp1->getId(),
])
->findOne()
);
$this->assertNotNull(
$em->getRDBRepository(Opportunity::ENTITY_TYPE)
->where([
Attribute::ID => $opp2->getId(),
])
->findOne()
);
$this->assertNotNull(
$em->getRDBRepository(Task::ENTITY_TYPE)
->where([
Attribute::ID => $task1->getId(),
])
->findOne()
);
}
}

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\integration\Espo\Record;
use Espo\Core\Acl\Table;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Entities\Portal;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\CaseObj;
use Espo\Modules\Crm\Entities\Contact;
use tests\integration\Core\BaseTestCase;
class DefaultsPopulatorTest extends BaseTestCase
{
/**
* @throws BadRequest
* @throws Forbidden
* @throws Conflict
*/
public function testPortal(): void
{
$em = $this->getEntityManager();
$account = $em->getRDBRepositoryByClass(Account::class)->getNew();
$em->saveEntity($account);
$contact = $em->getRDBRepositoryByClass(Contact::class)->getNew();
$contact->setAccount($account);
$em->saveEntity($contact);
$portal = $em->getRDBRepositoryByClass(Portal::class)->getNew();
$em->saveEntity($portal);
$this->createUser([
'userName' => 'tester',
'portalsIds' => [$portal->getId()],
'contactId' => $contact->getId(),
'accountsIds' => [$account->getId()],
], [
'data' => [
'Case' => [
'create' => Table::LEVEL_YES,
'read' => 'contact',
],
],
], true);
$this->auth(userName: 'tester', portalId: $portal->getId());
$this->reCreateApplication(reuse: true);
$service = $this->getContainer()->getByClass(ServiceContainer::class)->getByClass(CaseObj::class);
$result = $service->create((object) [
'name' => 'Test',
], CreateParams::create());
$this->assertEquals($contact->getId(), $result->getEntity()->getContact()?->getId());
$this->assertEquals($account->getId(), $result->getEntity()->getAccount()?->getId());
}
}

View File

@@ -0,0 +1,129 @@
<?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\integration\Espo\Record;
use Espo\Core\Exceptions\ConflictSilent;
use Espo\Core\Record\CreateParams;
use tests\integration\Core\BaseTestCase;
class DuplicateFindTest extends BaseTestCase
{
public function testAccount1()
{
$service = $this->getContainer()
->get('recordServiceContainer')
->get('Account');
$data1 = (object) [
'name' => 'test1',
'emailAddress' => 'test@test.com',
];
$service->create($data1, CreateParams::create());
$this->expectException(ConflictSilent::class);
$data2 = (object) [
'name' => 'test2',
'emailAddress' => 'test@test.com',
];
$service->create($data2, CreateParams::create());
}
public function testAccountSkip()
{
$service = $this->getContainer()
->get('recordServiceContainer')
->get('Account');
$data1 = (object) [
'name' => 'test1',
'emailAddress' => 'test@test.com',
];
$service->create($data1, CreateParams::create());
$data2 = (object) [
'name' => 'test2',
'emailAddress' => 'test@test.com',
];
$service->create($data2, CreateParams::create()->withSkipDuplicateCheck());
$this->assertTrue(true);
}
public function testLead1()
{
$service = $this->getContainer()
->get('recordServiceContainer')
->get('Lead');
$data1 = (object) [
'lastName' => 'test1',
'emailAddress' => 'test@test.com',
];
$service->create($data1, CreateParams::create());
$this->expectException(ConflictSilent::class);
$data2 = (object) [
'lastName' => 'test2',
'emailAddress' => 'test@test.com',
];
$service->create($data2, CreateParams::create());
}
public function testLeadSkip()
{
$service = $this->getContainer()
->get('recordServiceContainer')
->get('Lead');
$data1 = (object) [
'lastName' => 'test1',
'emailAddress' => 'test@test.com',
];
$service->create($data1, CreateParams::create());
$data2 = (object) [
'lastName' => 'test2',
'emailAddress' => 'test@test.com',
];
$service->create($data2, CreateParams::create()->withSkipDuplicateCheck());
$this->assertTrue(true);
}
}

View File

@@ -0,0 +1,87 @@
<?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\integration\Espo\Record;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\Duplicator\EntityDuplicator;
use Espo\Core\Record\ServiceContainer;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Task;
use tests\integration\Core\BaseTestCase;
class EntityDuplicatorTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testDuplicate(): void
{
$user = $this->createUser([
'userName' => 'test',
'type' => User::TYPE_ADMIN,
]);
$this->authenticate('test');
$em = $this->getEntityManager();
$account = $em->getRDBRepositoryByClass(Account::class)->getNew();
$account->setName('Account');
$em->saveEntity($account);
$email = $em->getRDBRepositoryByClass(Email::class)->getNew();
$email->setSubject('Test');
$email->setParent($account);
$em->saveEntity($email);
$taskService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Task::class);
$task = $taskService->create((object) [
'name' => 'Task',
'originalEmailId' => $email->getId(),
'parentId' => $account->getId(),
'parentType' => $account->getEntityType(),
'assignedUserId' => $user->getId(),
], CreateParams::create())->getEntity();
$this->assertEquals($email->getId(), $task->get('emailId'));
$duplicator = $this->getInjectableFactory()->create(EntityDuplicator::class);
$values = $duplicator->duplicate($task);
$this->assertEquals($account->getId(), $values->accountId ?? null);
$this->assertFalse(isset($values->emailId));
}
}

View File

@@ -0,0 +1,50 @@
<?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\integration\Espo\Record;
use Espo\Core\Record\EntityProvider;
use Espo\Modules\Crm\Entities\Account;
use tests\integration\Core\BaseTestCase;
class EntityProviderTest extends BaseTestCase
{
public function testGet(): void
{
$account = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE);
$id = $account->getId();
$provider = $this->getInjectableFactory()->create(EntityProvider::class);
/** @noinspection PhpUnhandledExceptionInspection */
$account = $provider->getByClass(Account::class, $account->getId());
$this->assertEquals($id, $account->getId());
}
}

View File

@@ -0,0 +1,727 @@
<?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\integration\Espo\Record;
use Espo\Core\Application;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\FieldValidation\Type;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Record\UpdateParams;
use Espo\Core\Utils\Config\ConfigWriter;
use Espo\Core\Utils\Metadata;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Lead;
use Espo\ORM\EntityManager;
use Espo\Tools\App\SettingsService as SettingsService;
use tests\integration\Core\BaseTestCase;
class FieldValidationTest extends BaseTestCase
{
private function setFieldsDefs(Application $app, string $entityType, array $data)
{
$metadata = $app->getContainer()->getByClass(Metadata::class);
$metadata->set('entityDefs', $entityType, [
'fields' => $data,
]);
$metadata->save();
}
public function testRequiredVarchar1()
{
$this->setFieldsDefs($this->getApplication(), 'Account', [
'name' => [
'required' => true,
],
]);
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->create((object) [
'name' => null,
], CreateParams::create());
}
public function testUpdateRequiredVarchar1()
{
$this->setFieldsDefs($this->getApplication(), 'Account', [
'name' => [
'required' => true,
],
]);
$entity = $this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->create(
(object) [
'name' => 'test'
],
CreateParams::create()
)
->getEntity();
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->update(
$entity->getId(),
(object) [
'name' => '',
],
UpdateParams::create()
);
}
public function testRequiredVarchar2()
{
$this->setFieldsDefs($this->getApplication(), 'Account', [
'name' => [
'required' => true,
],
]);
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->create(
(object) [
],
CreateParams::create()
);
}
public function testRequiredVarchar3()
{
$this->setFieldsDefs($this->getApplication(), 'Account', [
'name' => [
'required' => true,
],
]);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->create((object) [
'name' => 'test',
], CreateParams::create());
$this->assertTrue(true);
}
public function testMaxLength1()
{
$this->setFieldsDefs($this->getApplication(), 'Account', [
'name' => [
'required' => true,
'maxLength' => 5,
],
]);
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->create((object) [
'name' => '123456',
], CreateParams::create());
}
public function testMaxLength2()
{
$this->setFieldsDefs($this->getApplication(), 'Account', [
'name' => [
'required' => true,
'maxLength' => 5,
]
]);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->create((object) [
'name' => '12345',
], CreateParams::create());
$this->assertTrue(true);
}
public function testRequiredLink1()
{
$this->setFieldsDefs($this->getApplication(), 'Account', [
'assignedUser' => [
'required' => true,
]
]);
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->create((object) [
'name' => 'test',
'assignedUserId' => null,
], CreateParams::create());
}
private function getAdminUser(): User
{
$repository = $this->getContainer()
->getByClass(EntityManager::class)
->getRDBRepositoryByClass(User::class);
$user = $repository
->where(['type' => User::TYPE_ADMIN])
->findOne();
if (!$user) {
$user = $repository->getNew();
$user->set('userName', 'test-admin');
$user->set('type', User::TYPE_ADMIN);
$repository->save($user);
}
return $user;
}
public function testRequiredLink2()
{
$this->setFieldsDefs($this->getApplication(), 'Account', [
'assignedUser' => [
'required' => true,
]
]);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->create((object) [
'name' => 'test',
'assignedUserId' => $this->getAdminUser()->getId(),
], CreateParams::create());
$this->assertTrue(true);
}
public function testRequiredLinkMultiple1()
{
$this->setFieldsDefs($this->getApplication(), 'Account', [
'teams' => [
'required' => true,
]
]);
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->create((object) [
'name' => 'test',
'teamsIds' => [],
], CreateParams::create());
}
public function testRequiredCurrency1()
{
$this->setFieldsDefs($this->getApplication(), 'Lead', [
'opportunityAmount' => [
'required' => true,
]
]);
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Lead')
->create((object) [
'lastName' => 'test',
'opportunityAmount' => null,
], CreateParams::create());
}
public function testRequiredCurrency2()
{
$this->setFieldsDefs($this->getApplication(), 'Lead', [
'opportunityAmount' => [
'required' => true,
]
]);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Lead')
->create((object) [
'lastName' => 'test',
'opportunityAmount' => 100,
'opportunityAmountCurrency' => null,
], CreateParams::create());
$this->assertTrue(true);
}
public function testRequiredCurrency3()
{
$this->setFieldsDefs($this->getApplication(), 'Lead', [
'opportunityAmount' => [
'required' => true,
]
]);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Lead')
->create((object) [
'lastName' => 'test',
'opportunityAmount' => 100,
'opportunityAmountCurrency' => 'USD',
], CreateParams::create());
$this->assertTrue(true);
}
public function testRequiredEnum1()
{
$this->setFieldsDefs($this->getApplication(), 'Lead', [
'status' => [
'required' => true,
'default' => null,
]
]);
$this->getDataManager()->clearCache();
$this->getDataManager()->rebuildMetadata();
$this->authenticate(null);
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Lead')
->create((object) [
'lastName' => 'test',
], CreateParams::create());
}
public function testRequiredEnum2()
{
$this->setFieldsDefs($this->getApplication(), 'Lead', [
'status' => [
'required' => true,
]
]);
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Lead')
->create((object) [
'lastName' => 'test',
'status' => null,
], CreateParams::create());
}
public function testRequiredEnum3()
{
$this->setFieldsDefs($this->getApplication(), 'Lead', [
'status' => [
'required' => true,
]
]);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Lead')
->create((object) [
'lastName' => 'test',
'status' => 'New',
], CreateParams::create());
$this->assertTrue(true);
}
public function testSkipRequired()
{
/** @noinspection PhpUnhandledExceptionInspection */
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Meeting')
->create((object) [
'name' => 'test',
'dateStart' => '2021-01-01 00:00:00',
'duration' => 1000,
'assignedUserId' => $this->getAdminUser()->getId(),
], CreateParams::create());
$this->assertTrue(true);
}
public function testSettings()
{
$service = $this->getInjectableFactory()->create(SettingsService::class);
$this->expectException(BadRequest::class);
$service->setConfigData((object) [
'activitiesEntityList' => 'should-be-array',
]);
}
public function testCurrencyValid(): void
{
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Lead::class);
/** @noinspection PhpUnhandledExceptionInspection */
$service->create((object) [
'lastName' => 'Test 1',
'opportunityAmount' => '100.10',
], CreateParams::create());
/** @noinspection PhpUnhandledExceptionInspection */
$service->create((object) [
'lastName' => 'Test 2',
'opportunityAmount' => '100',
], CreateParams::create());
/** @noinspection PhpUnhandledExceptionInspection */
$service->create((object) [
'lastName' => 'Test 3',
'opportunityAmount' => '',
], CreateParams::create());
/** @noinspection PhpUnhandledExceptionInspection */
$service->create((object) [
'lastName' => 'Test 4',
'opportunityAmount' => null,
], CreateParams::create());
$this->expectException(BadRequest::class);
/** @noinspection PhpUnhandledExceptionInspection */
$service->create((object) [
'lastName' => 'Test Bad 1',
'opportunityAmount' => 'bad-value',
], CreateParams::create());
}
public function testPhoneNumber(): void
{
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Account::class);
$configWriter = $this->getInjectableFactory()->create(ConfigWriter::class);
$configWriter->set('phoneNumberExtensions', true);
$configWriter->save();
/** @noinspection PhpUnhandledExceptionInspection */
$service->create((object)[
'name' => 'Test 1',
'phoneNumberData' => [
(object)[
'phoneNumber' => '+38 09 044 433 22 ext. 001',
],
],
], CreateParams::create());
$thrown = false;
try {
/** @noinspection PhpUnhandledExceptionInspection */
$service->create((object)[
'name' => 'Test 2',
'phoneNumberData' => [
(object)[
'phoneNumber' => '+38 09 044 433 22 ext. ABC',
],
(object)[
'phoneNumber' => '+38 09 044 433 33 ext. 1234567',
],
],
], CreateParams::create());
} catch (BadRequest) {
$thrown = true;
}
$this->assertTrue($thrown);
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testDynamicLogicRequired(): void
{
$metadata = $this->getMetadata();
$metadata->set('logicDefs', Account::ENTITY_TYPE, [
'fields' => [
'description' => [
Type::REQUIRED => [
'conditionGroup' => [
[
'type' => 'equals',
'attribute' => 'type',
'value' => 'Customer',
]
]
]
]
]
]);
$metadata->save();
$this->getDataManager()->clearCache();
$this->getDataManager()->rebuildMetadata();
$service = $this->getContainer()->getByClass(ServiceContainer::class)->getByClass(Account::class);
$account = $service->create((object) [
'name' => 'Test',
], CreateParams::create())->getEntity();
$isThrown = false;
try {
$service->update($account->getId(), (object) [
'type' => 'Customer',
], UpdateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertTrue($isThrown);
$isThrown = false;
try {
$service->update($account->getId(), (object) [
'type' => 'Customer',
'description' => 'Test.',
], UpdateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertFalse($isThrown);
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testDecimal(): void
{
$metadata = $this->getMetadata();
$metadata->set('entityDefs', 'Account', [
'fields' => [
'test' => [
'type' => FieldType::DECIMAL,
'min' => '-1.0',
'max' => '100.0',
]
],
]);
$metadata->save();
$this->getDataManager()->rebuild();
$this->reCreateApplication();
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Account::class);
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-0',
'test' => null,
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertFalse($isThrown);
/** @noinspection PhpConditionAlreadyCheckedInspection */
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-1',
'test' => '10.0',
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertFalse($isThrown);
/** @noinspection PhpConditionAlreadyCheckedInspection */
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-2',
'test' => '-1.0',
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertFalse($isThrown);
/** @noinspection PhpConditionAlreadyCheckedInspection */
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-3',
'test' => '100.0',
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertFalse($isThrown);
/** @noinspection PhpConditionAlreadyCheckedInspection */
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-4',
'test' => '-10.0',
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertTrue($isThrown);
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-5',
'test' => '100.1',
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertTrue($isThrown);
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-6',
'test' => 'abc',
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertTrue($isThrown);
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-7',
'test' => ['test'],
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertTrue($isThrown);
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-8',
'test' => 10.0,
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertFalse($isThrown);
/** @noinspection PhpConditionAlreadyCheckedInspection */
$isThrown = false;
try {
$service->create((object) [
'name' => 'Test-9',
'test' => 10,
], CreateParams::create());
} catch (BadRequest) {
$isThrown = true;
}
$this->assertFalse($isThrown);
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testDependsOn(): void
{
$em = $this->getEntityManager();
$account = $em->createEntity('Account', [
'name' => 'Test',
]);
$this->setFieldsDefs($this->getApplication(), 'Account', [
'description' => [
'required' => true,
'validationDependsOnFieldList' => ['name'],
],
]);
$this->getDataManager()->clearCache();
$this->getDataManager()->rebuildMetadata();
$this->expectException(BadRequest::class);
$this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account')
->update($account->getId(), (object) [
'name' => 'Test 1',
], UpdateParams::create());
}
}

View File

@@ -0,0 +1,653 @@
<?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\integration\Espo\Record;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Field\Date;
use Espo\Core\Name\Field;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Record\UpdateParams;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Attachment;
use Espo\Entities\Email;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\CaseObj;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Document;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Modules\Crm\Entities\Opportunity;
use Espo\Modules\Crm\Entities\Task;
use Espo\ORM\EntityManager;
use Espo\ORM\Type\RelationType;
use tests\integration\Core\BaseTestCase;
class LinkTest extends BaseTestCase
{
public function testUnlinkRequired1(): void
{
$metadata = $this->getContainer()->getByClass(Metadata::class);
$metadata->set('entityDefs', CaseObj::ENTITY_TYPE, [
'fields' => [
'account' => ['required' => true]
]
]);
$metadata->save();
$this->reCreateApplication(reuse: true);
$em = $this->getContainer()->getByClass(EntityManager::class);
$account = $em->createEntity(Account::ENTITY_TYPE, [
'name' => 'Test',
]);
$case = $em->createEntity(CaseObj::ENTITY_TYPE, [
'name' => 'Test',
'accountId' => $account->getId(),
]);
$accountService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Account::class);
$this->expectException(Forbidden::class);
/** @noinspection PhpUnhandledExceptionInspection */
$accountService->unlink($account->getId(), 'cases', $case->getId());
}
public function testLinkCheck1(): void
{
$user = $this->createUser('test', [
'data' => [
'Account' => [
'create' => 'no',
'read' => 'own',
'edit' => 'no',
'delete' => 'no',
],
'Opportunity' => [
'create' => 'yes',
'read' => 'own',
'edit' => 'own',
'delete' => 'no',
],
'Task' => [
'create' => 'yes',
'read' => 'own',
'edit' => 'own',
'delete' => 'no',
],
]
]);
$em = $this->getContainer()->getByClass(EntityManager::class);
$account1 = $em->createEntity(Account::ENTITY_TYPE, [
'name' => '1',
'assignedUserId' => $user->getId(),
]);
$account2 = $em->createEntity(Account::ENTITY_TYPE, [
'name' => '2',
]);
$this->authenticate('test');
$oppService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Opportunity::class);
/** @noinspection PhpUnhandledExceptionInspection */
$oppService->create((object) [
'name' => '1',
'accountId' => $account1->getId(),
'assignedUserId' => $user->getId(),
'amount' => 1.0,
'amountCurrency' => 'USD',
'probability' => 10,
'closeDate' => Date::createToday()->toString(),
], CreateParams::create());
$isThrown = false;
try {
/** @noinspection PhpUnhandledExceptionInspection */
$oppService->create((object) [
'name' => '2',
'accountId' => $account2->getId(),
'assignedUserId' => $user->getId(),
'amount' => 1.0,
'amountCurrency' => 'USD',
'probability' => 10,
'closeDate' => Date::createToday()->toString(),
], CreateParams::create());
}
catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
//
$taskService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Task::class);
/** @noinspection PhpUnhandledExceptionInspection */
$task1 = $taskService->create((object) [
'name' => '1',
'accountId' => $account1->getId(),
'parentType' => $account1->getEntityType(),
'assignedUserId' => $user->getId(),
], CreateParams::create())->getEntity();
$isThrown = false;
try {
/** @noinspection PhpUnhandledExceptionInspection */
$taskService->create((object) [
'name' => '2',
'parentId' => $account2->getId(),
'parentType' => $account2->getEntityType(),
'assignedUserId' => $user->getId(),
], CreateParams::create());
}
catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
//
$isThrown = false;
try {
/** @noinspection PhpUnhandledExceptionInspection */
$taskService->update($task1->getId(), (object) [
'parentId' => $account2->getId(),
'parentType' => $account2->getEntityType(),
], UpdateParams::create())->getEntity();
}
catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
//
$metadata = $this->getContainer()->getByClass(Metadata::class);
$metadata->set('entityDefs', Opportunity::ENTITY_TYPE, [
'fields' => [
'account' => [
'defaultAttributes' => [
'accountId' => $account2->getId(),
]
]
]
]);
$metadata->save();
$this->reCreateApplication();
$oppService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Opportunity::class);
/** @noinspection PhpUnhandledExceptionInspection */
$oppService->create((object) [
'name' => '2',
'accountId' => $account2->getId(),
'assignedUserId' => $user->getId(),
'amount' => 1.0,
'amountCurrency' => 'USD',
'probability' => 10,
'closeDate' => Date::createToday()->toString(),
], CreateParams::create());
}
public function testLinkCheckEmailToCase(): void
{
$user = $this->createUser('test', [
'data' => [
'Account' => [
'create' => 'no',
'read' => 'own',
'edit' => 'no',
'delete' => 'no',
],
'Case' => [
'create' => 'yes',
'read' => 'own',
'edit' => 'own',
'delete' => 'no',
],
'Contact' => [
'create' => 'yes',
'read' => 'own',
'edit' => 'own',
'delete' => 'no',
],
'Lead' => [
'create' => 'yes',
'read' => 'own',
'edit' => 'own',
'delete' => 'no',
],
'Email' => [
'create' => 'yes',
'read' => 'own',
'edit' => 'own',
'delete' => 'no',
],
],
]);
$em = $this->getEntityManager();
$account = $em->createEntity(Account::ENTITY_TYPE, ['assignedUserId' => $user->getId()]);
$lead = $em->createEntity(Lead::ENTITY_TYPE);
$contact = $em->createEntity(Contact::ENTITY_TYPE, ['lastName' => 'contact']);
$email = $em->createEntity(Email::ENTITY_TYPE, [
'assignedUserId' => $user->getId(),
'parentId' => $lead->getId(),
'parentType' => Lead::ENTITY_TYPE,
]);
$this->authenticate('test');
$caseService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(CaseObj::class);
// Should not create if no access to the Lead.
$isThrown = false;
try {
/** @noinspection PhpUnhandledExceptionInspection */
$caseService->create((object) [
'name' => '1',
'assignedUserId' => $user->getId(),
'leadId' => $lead->getId(),
'accountId' => $account->getId(),
], CreateParams::create());
}
catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
// Should create if an email with a Lead parent is passed.
/** @noinspection PhpUnhandledExceptionInspection */
$caseService->create((object) [
'name' => '1',
'assignedUserId' => $user->getId(),
'leadId' => $lead->getId(),
'accountId' => $account->getId(),
'originalEmailId' => $email->getId(),
], CreateParams::create());
//
$account = $em->createEntity(Account::ENTITY_TYPE);
$email = $em->createEntity(Email::ENTITY_TYPE, [
'assignedUserId' => $user->getId(),
'parentId' => $account->getId(),
'parentType' => Account::ENTITY_TYPE,
]);
// Should not create if no access to the Account.
$isThrown = false;
try {
/** @noinspection PhpUnhandledExceptionInspection */
$caseService->create((object) [
'name' => '1',
'assignedUserId' => $user->getId(),
'accountId' => $account->getId(),
], CreateParams::create());
}
catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
// Should create if an email with an Account parent is passed.
/** @noinspection PhpUnhandledExceptionInspection */
$caseService->create((object) [
'name' => '1',
'assignedUserId' => $user->getId(),
'accountId' => $account->getId(),
'originalEmailId' => $email->getId(),
], CreateParams::create());
//
$account = $em->createEntity(Account::ENTITY_TYPE, ['assignedUserId' => $user->getId()]);
$email = $em->createEntity(Email::ENTITY_TYPE, [
'assignedUserId' => $user->getId(),
'parentId' => $contact->getId(),
'parentType' => Contact::ENTITY_TYPE,
]);
// Should not create if no access to the Contact.
$isThrown = false;
try {
/** @noinspection PhpUnhandledExceptionInspection */
$caseService->create((object) [
'name' => '1',
'assignedUserId' => $user->getId(),
'accountId' => $account->getId(),
'contactId' => $contact->getId(),
'contactsIds' => [$contact->getId()],
], CreateParams::create());
}
catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
// Should create if an email with a Contact parent is passed.
/** @noinspection PhpUnhandledExceptionInspection */
$caseService->create((object) [
'name' => '1',
'assignedUserId' => $user->getId(),
'accountId' => $account->getId(),
'contactId' => $contact->getId(),
'contactsIds' => [$contact->getId()],
'originalEmailId' => $email->getId(),
], CreateParams::create());
// Should not allow more than 1 ID.
$contactAnother = $this->getEntityManager()->createEntity(Contact::ENTITY_TYPE);
$isThrown = false;
try {
/** @noinspection PhpUnhandledExceptionInspection */
$caseService->create((object) [
'name' => '1',
'assignedUserId' => $user->getId(),
'contactId' => $contact->getId(),
'contactsIds' => [$contact->getId(), $contactAnother->getId()],
'originalEmailId' => $email->getId(),
], CreateParams::create());
}
catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
}
public function testLoadNames(): void
{
$caseService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(CaseObj::class);
$em = $this->getEntityManager();
$contact = $em->createEntity(Contact::ENTITY_TYPE, ['lastName' => 'contact']);
$contact1 = $em->createEntity(Contact::ENTITY_TYPE, ['lastName' => 'contact1']);
/** @noinspection PhpUnhandledExceptionInspection */
$case = $caseService->create((object) [
'name' => '1',
'contactId' => $contact->getId(),
'contactsIds' => [$contact->getId()],
], CreateParams::create())->getEntity();
$this->assertEquals(
(object) [
$contact->getId() => $contact->get('lastName'),
],
$case->get('contactsNames')
);
/** @noinspection PhpUnhandledExceptionInspection */
$case = $caseService->update($case->getId(), (object) [
'contactsIds' => [$contact->getId(), $contact1->getId()],
], UpdateParams::create())->getEntity();
$this->assertEquals(
(object) [
$contact->getId() => $contact->get('lastName'),
$contact1->getId() => $contact1->get('lastName'),
],
$case->get('contactsNames')
);
}
public function testOneToOne(): void
{
$metadata = $this->getContainer()->getByClass(Metadata::class);
$metadata->set('entityDefs', CaseObj::ENTITY_TYPE, [
'links' => [
'task' => [
'foreign' => 'case',
'type' => RelationType::BELONGS_TO,
'entity' => Task::ENTITY_TYPE,
],
]
]);
$metadata->set('entityDefs', Task::ENTITY_TYPE, [
'fields' => [
'case' => [
'type' => FieldType::LINK_ONE,
],
],
'links' => [
'case' => [
'foreign' => 'task',
'type' => RelationType::HAS_ONE,
'entity' => CaseObj::ENTITY_TYPE,
],
]
]);
$metadata->save();
/** @noinspection PhpUnhandledExceptionInspection */
$this->getDataManager()->rebuild();
$this->reCreateApplication();
$em = $this->getEntityManager();
$task = $em->getRDBRepositoryByClass(Task::class)->getNew();
$task->setMultiple(['name' => 'Task']);
$em->saveEntity($task);
$case = $em->getRDBRepositoryByClass(CaseObj::class)->getNew();
$case->setMultiple(['name' => 'Case']);
$em->saveEntity($case);
$service = $this->getContainer()->getByClass(ServiceContainer::class)->getByClass(Task::class);
/** @noinspection PhpUnhandledExceptionInspection */
$service->update($task->getId(), (object) [
'caseId' => $case->getId(),
], UpdateParams::create());
$this->assertTrue(
$em->getRDBRepositoryByClass(Task::class)
->getRelation($task, 'case')
->isRelatedById($case->getId())
);
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testAttachmentsOne(): void
{
$em = $this->getEntityManager();
$attachment1 = $em->getRDBRepositoryByClass(Attachment::class)->getNew();
$em->saveEntity($attachment1);
$attachment2 = $em->getRDBRepositoryByClass(Attachment::class)->getNew();
$em->saveEntity($attachment2);
$service = $this->getContainer()->getByClass(ServiceContainer::class)->getByClass(Document::class);
$document1 = $service->create((object) [
Field::NAME => 'Good',
Document::FIELD_FILE . 'Id' => $attachment1->getId(),
Document::FIELD_PUBLISH_DATE => Date::createToday()->toString(),
])->getEntity();
//
$throw = false;
try {
$service->create((object) [
Field::NAME => 'Bad',
Document::FIELD_FILE . 'Id' => $attachment1->getId(),
Document::FIELD_PUBLISH_DATE => Date::createToday()->toString(),
]);
} catch (Forbidden) {
$throw = true;
}
$this->assertTrue($throw, 'Should not allow to link already linked attachments.');
//
$service->create((object) [
Field::NAME => 'Good 2',
Document::FIELD_FILE . 'Id' => $attachment2->getId(),
Document::FIELD_PUBLISH_DATE => Date::createToday()->toString(),
]);
//
$throw = false;
try {
$service->update($document1->getId(), (object) [
Document::FIELD_FILE . 'Id' => $attachment2->getId(),
]);
} catch (Forbidden) {
$throw = true;
}
$this->assertTrue($throw, 'Should not allow to link already linked attachments.');
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testAttachmentsMany(): void
{
$em = $this->getEntityManager();
$attachment1 = $em->getRDBRepositoryByClass(Attachment::class)->getNew();
$em->saveEntity($attachment1);
$attachment2 = $em->getRDBRepositoryByClass(Attachment::class)->getNew();
$em->saveEntity($attachment2);
$attachment3 = $em->getRDBRepositoryByClass(Attachment::class)->getNew();
$em->saveEntity($attachment3);
$service = $this->getContainer()->getByClass(ServiceContainer::class)->getByClass(CaseObj::class);
$case1 = $service->create((object) [
Field::NAME => 'Good',
CaseObj::FIELD_ATTACHMENTS . 'Ids' => [$attachment1->getId()],
])->getEntity();
//
$throw = false;
try {
$service->create((object) [
Field::NAME => 'Bad',
CaseObj::FIELD_ATTACHMENTS . 'Ids' => [$attachment1->getId()],
]);
} catch (Forbidden) {
$throw = true;
}
$this->assertTrue($throw, 'Should not allow to link already linked attachments.');
//
$case2 = $service->create((object) [
Field::NAME => 'Good 2',
CaseObj::FIELD_ATTACHMENTS . 'Ids' => [$attachment3->getId()],
])->getEntity();
//
$service->update($case1->getId(), (object) [
CaseObj::FIELD_ATTACHMENTS . 'Ids' => [$attachment1->getId(), $attachment2->getId()],
]);
//
$throw = false;
try {
$service->update($case2->getId(), (object) [
CaseObj::FIELD_ATTACHMENTS . 'Ids' => [$attachment2->getId()],
]);
} catch (Forbidden) {
$throw = true;
}
$this->assertTrue($throw, 'Should not allow to link already linked attachments.');
}
}

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\integration\Espo\Record;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Record\UpdateParams;
use Espo\Core\Utils\Metadata;
use Espo\Modules\Crm\Entities\Account;
use tests\integration\Core\BaseTestCase;
class ReadOnlyTest extends BaseTestCase
{
public function testReadOnly(): void
{
$metadata = $this->getContainer()->getByClass(Metadata::class);
$metadata->set('entityDefs', Account::ENTITY_TYPE, [
'fields' => [
'name' => [
'readOnlyAfterCreate' => true,
],
'type' => [
'readOnly' => true,
],
]
]);
$metadata->save();
$this->reCreateApplication(reuse: true);
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Account::class);
$account = $service->create((object) [
'name' => 'Test',
'type' => 'Customer',
], CreateParams::create())->getEntity();
$this->assertEquals('Test', $account->get('name'));
$this->assertNull($account->get('type'));
$account = $service->update($account->getId(), (object) [
'name' => 'Test 1',
'billingAddressCity' => 'Hello',
], UpdateParams::create())->getEntity();
$this->assertEquals('Test', $account->get('name'));
$this->assertEquals('Hello', $account->get('billingAddressCity'));
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testReadOnlyDynamicLogic(): void
{
$metadata = $this->getMetadata();
$metadata->set('logicDefs', Account::ENTITY_TYPE, [
'fields' => [
'description' => [
'readOnlySaved' => [
'conditionGroup' => [
[
'type' => 'equals',
'attribute' => 'type',
'value' => 'Customer',
]
]
]
]
]
]);
$metadata->save();
$this->reCreateApplication(reuse: true);
$service = $this->getContainer()->getByClass(ServiceContainer::class)->getByClass(Account::class);
$account = $service->create((object) [
'name' => 'Test',
'description' => '1',
], CreateParams::create())->getEntity();
$service->update($account->getId(), (object) [
'type' => 'Customer',
], UpdateParams::create());
$account = $service->update($account->getId(), (object) [
'description' => '2',
], UpdateParams::create());
$this->assertEquals('1', $account->getValueMap()->description);
}
}

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\integration\Espo\Record;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ReadParams;
use Espo\Core\Record\DeleteParams;
use Espo\Core\Record\ServiceContainer;
use tests\integration\Core\BaseTestCase;
class RestoreDeletedTest extends BaseTestCase
{
public function testDeleted(): void
{
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account');
$account = $service->create((object) [
'name' => 'Test'
], CreateParams::create())->getEntity();
$service->delete($account->getId(), DeleteParams::create());
$account = $service->read($account->getId(), ReadParams::create());
$this->assertNotNull($account);
$this->assertTrue($account->get('deleted'));
}
public function testRestoreDeleted()
{
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->get('Account');
$account = $service->create((object) [
'name' => 'Test'
], CreateParams::create())->getEntity();
$service->delete($account->getId(), DeleteParams::create());
$service->restoreDeleted($account->getId());
$account = $service->read($account->getId(), ReadParams::create())->getEntity();
$this->assertNotNull($account);
$this->assertFalse($account->get('deleted'));
}
}

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\integration\Espo\Record;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Utils\Config\ConfigWriter;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\Modules\Crm\Entities\Opportunity;
use Espo\Modules\Crm\Entities\Task;
use Espo\Tools\FieldManager\FieldManager;
use tests\integration\Core\BaseTestCase;
class SanitizeTest extends BaseTestCase
{
public function testSanitize(): void
{
// phone
/** @noinspection PhpUnhandledExceptionInspection */
$this->getInjectableFactory()
->create(FieldManager::class)
->create(Account::ENTITY_TYPE, 'array', ['type' => 'array']);
/** @noinspection PhpUnhandledExceptionInspection */
$this->getDataManager()->rebuild();
$this->reCreateApplication();
$em = $this->getEntityManager();
$user = $em->getRDBRepositoryByClass(User::class)->getNew();
$user->setUserName('test');
$em->saveEntity($user);
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Account::class);
/** @noinspection PhpUnhandledExceptionInspection */
/** @var Account $account */
$account = $service->create((object) [
'name' => ' Test 1 ',
'sicCode' => ' ',
'phoneNumber' => '+380 9044 433 11',
'description' => '',
'cArray' => [
' test ',
'hello',
],
], CreateParams::create())->getEntity();
$numbers = $account->getPhoneNumberGroup()->getNumberList();
$this->assertCount(1, $numbers);
$this->assertEquals('+380904443311', $numbers[0]);
$this->assertEquals('Test 1', $account->getName());
$this->assertEquals(null, $account->get('sicCode'));
$this->assertEquals(null, $account->get('description'));
$this->assertEquals(['test', 'hello'], $account->get('cArray'));
/** @noinspection PhpUnhandledExceptionInspection */
/** @var Account $account */
$account = $service->create((object) [
'name' => 'Test 2',
'phoneNumberData' => [
(object) [
'phoneNumber' => '+38 09 044 433 22',
],
(object) [
'phoneNumber' => '+38 09 044 433 33',
],
],
'description' => 'Test',
'cArray' => null,
], CreateParams::create())->getEntity();
$this->assertEquals('Test', $account->get('description'));
$this->assertEquals([], $account->get('cArray'));
$numbers = $account->getPhoneNumberGroup()->getNumberList();
$this->assertCount(2, $numbers);
sort($numbers);
$this->assertEquals('+380904443322', $numbers[0]);
$this->assertEquals('+380904443333', $numbers[1]);
$configWriter = $this->getInjectableFactory()->create(ConfigWriter::class);
$configWriter->set('phoneNumberExtensions', true);
$configWriter->save();
/** @var Account $account */
/** @noinspection PhpUnhandledExceptionInspection */
$account = $service->create((object) [
'name' => 'Test 3',
'phoneNumberData' => [
(object) [
'phoneNumber' => '+38 09 044 433 22 ext. 0001',
],
(object) [
'phoneNumber' => '+38 09 044 433 33 x. 1001',
],
(object) [
'phoneNumber' => '+380904443344x.1000',
],
(object) [
'phoneNumber' => '+380904443355#1000',
],
(object) [
'phoneNumber' => '+380904443366 # 1000',
],
],
], CreateParams::create())->getEntity();
$numbers = $account->getPhoneNumberGroup()->getNumberList();
$this->assertCount(5, $numbers);
sort($numbers);
$this->assertEquals('+380904443322 ext. 0001', $numbers[0]);
$this->assertEquals('+380904443333 ext. 1001', $numbers[1]);
$this->assertEquals('+380904443344 ext. 1000', $numbers[2]);
$this->assertEquals('+380904443355 ext. 1000', $numbers[3]);
$this->assertEquals('+380904443366 ext. 1000', $numbers[4]);
// datetime
/** @noinspection PhpUnhandledExceptionInspection */
$meeting = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Meeting::class)
->create((object) [
'name' => 'Test',
'dateStart' => '2030-12-10 10:11:12',
'dateEnd' => '2030-12-10T10:11:12-01:00',
'assignedUserId' => $user->getId(),
], CreateParams::create())->getEntity();
$this->assertEquals('2030-12-10 10:11:12', $meeting->get('dateStart'));
$this->assertEquals('2030-12-10 11:11:12', $meeting->get('dateEnd'));
// datetimeOptional
/** @noinspection PhpUnhandledExceptionInspection */
$task = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Task::class)
->create((object) [
'name' => 'Test',
'dateStartDate' => '2030-12-10T10:11:12-01:00',
'dateEnd' => '2030-12-10T10:11:12-01:00',
'assignedUserId' => $user->getId(),
], CreateParams::create())->getEntity();
$this->assertEquals('2030-12-10', $task->get('dateStartDate'));
$this->assertEquals('2030-12-10 11:11:12', $task->get('dateEnd'));
/** @noinspection PhpUnhandledExceptionInspection */
$task = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Task::class)
->create((object) [
'name' => 'Test',
'dateStartDate' => '2030-12-10',
'assignedUserId' => $user->getId(),
], CreateParams::create())->getEntity();
$this->assertEquals('2030-12-10', $task->get('dateStartDate'));
// date
/** @noinspection PhpUnhandledExceptionInspection */
$meeting = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Opportunity::class)
->create((object) [
'name' => 'Test',
'closeDate' => '2030-12-10T10:11:12-01:00',
'assignedUserId' => $user->getId(),
'probability' => 10,
'amount' => 1.0,
], CreateParams::create())->getEntity();
$this->assertEquals('2030-12-10', $meeting->get('closeDate'));
/** @noinspection PhpUnhandledExceptionInspection */
$meeting = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Opportunity::class)
->create((object) [
'name' => 'Test',
'closeDate' => '2030-12-10',
'assignedUserId' => $user->getId(),
'probability' => 10,
'amount' => 1.0,
], CreateParams::create())->getEntity();
$this->assertEquals('2030-12-10', $meeting->get('closeDate'));
}
public function testForeign(): void
{
$account = $this->getEntityManager()->createEntity(Account::ENTITY_TYPE, [
'type' => Account::TYPE_CUSTOMER,
]);
$serviceContainer = $this->getContainer()->getByClass(ServiceContainer::class);
$contactService = $serviceContainer->getByClass(Contact::class);
$data = (object) [
'lastName' => 'C-1',
'accountId' => $account->getId(),
];
$contactService->sanitizeInput($data);
$this->assertEquals(Account::TYPE_CUSTOMER, $data->accountType ?? null);
$data = (object) [
'lastName' => 'C-2',
];
$contactService->sanitizeInput($data);
$this->assertEquals(null, $data->accountType ?? null);
}
}

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\integration\Espo\Record;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Record\UpdateParams;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use tests\integration\Core\BaseTestCase;
class UpdateTest extends BaseTestCase
{
public function testUpdateForeign(): void
{
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Contact::class);
$em = $this->getEntityManager();
$account1 = $em->createEntity(Account::ENTITY_TYPE, ['type' => Account::TYPE_CUSTOMER]);
$account2 = $em->createEntity(Account::ENTITY_TYPE, ['type' => Account::TYPE_PARTNER]);
$contact = $em->createEntity(Contact::ENTITY_TYPE, ['accountId' => $account1->getId()]);
/** @noinspection PhpUnhandledExceptionInspection */
$contact = $service->update(
$contact->getId(),
(object) ['accountId' => $account2->getId()],
UpdateParams::create()
);
$this->assertEquals(Account::TYPE_PARTNER, $contact->get('accountType'));
}
}

View File

@@ -0,0 +1,155 @@
<?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\integration\Espo\Role;
use Espo\Core\Acl\Table;
use Espo\Core\Portal\Acl\Table as TablePortal;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Record\UpdateParams;
use Espo\Entities\PortalRole;
use Espo\Entities\Role;
use Espo\Modules\Crm\Entities\Account;
use tests\integration\Core\BaseTestCase;
class ServiceTest extends BaseTestCase
{
public function testCreateUpdate(): void
{
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Role::class);
$data = (object) [
Account::ENTITY_TYPE => (object) [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_ALL,
Table::ACTION_EDIT => Table::LEVEL_OWN,
Table::ACTION_DELETE => Table::LEVEL_NO,
Table::ACTION_STREAM => Table::LEVEL_TEAM,
],
'Activities' => true,
'ExternalAccount' => false,
];
$fieldData = (object) [
Account::ENTITY_TYPE => (object) [
'description' => (object) [
Table::ACTION_READ => Table::LEVEL_YES,
Table::ACTION_EDIT => Table::LEVEL_NO,
],
],
];
/** @noinspection PhpUnhandledExceptionInspection */
$role = $service->create((object) [
'name' => 'Test',
'data' => $data,
'fieldData' => $fieldData,
], CreateParams::create())->getEntity();
$this->assertEquals($data, $role->get('data'));
$this->assertEquals($fieldData, $role->get('fieldData'));
/** @noinspection PhpUnhandledExceptionInspection */
$role = $service->update($role->getId(), (object) [
'data' => (object) [],
'fieldData' => (object) [],
], UpdateParams::create())->getEntity();
$this->assertEquals((object) [], $role->get('data'));
$this->assertEquals((object) [], $role->get('fieldData'));
/** @noinspection PhpUnhandledExceptionInspection */
$role = $service->update($role->getId(), (object) [
'data' => $data,
'fieldData' => $fieldData,
], UpdateParams::create())->getEntity();
$this->assertEquals($data, $role->get('data'));
$this->assertEquals($fieldData, $role->get('fieldData'));
}
public function testPortalCreateUpdate(): void
{
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(PortalRole::class);
$data = (object) [
Account::ENTITY_TYPE => (object) [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_ALL,
Table::ACTION_EDIT => TablePortal::LEVEL_ACCOUNT,
Table::ACTION_DELETE => Table::LEVEL_NO,
Table::ACTION_STREAM => TablePortal::LEVEL_ACCOUNT,
],
'Activities' => true,
'ExternalAccount' => false,
];
$fieldData = (object) [
Account::ENTITY_TYPE => (object) [
'description' => (object) [
Table::ACTION_READ => Table::LEVEL_YES,
Table::ACTION_EDIT => Table::LEVEL_NO,
],
],
];
/** @noinspection PhpUnhandledExceptionInspection */
$role = $service->create((object) [
'name' => 'Test',
'data' => $data,
'fieldData' => $fieldData,
], CreateParams::create())->getEntity();
$this->assertEquals($data, $role->get('data'));
$this->assertEquals($fieldData, $role->get('fieldData'));
/** @noinspection PhpUnhandledExceptionInspection */
$role = $service->update($role->getId(), (object) [
'data' => (object) [],
'fieldData' => (object) [],
], UpdateParams::create())->getEntity();
$this->assertEquals((object) [], $role->get('data'));
$this->assertEquals((object) [], $role->get('fieldData'));
/** @noinspection PhpUnhandledExceptionInspection */
$role = $service->update($role->getId(), (object) [
'data' => $data,
'fieldData' => $fieldData,
], UpdateParams::create())->getEntity();
$this->assertEquals($data, $role->get('data'));
$this->assertEquals($fieldData, $role->get('fieldData'));
}
}

View File

@@ -0,0 +1,166 @@
<?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\integration\Espo\Settings;
use Espo\Entities\User;
use Espo\Tools\App\SettingsService;
use tests\integration\Core\BaseTestCase;
class AccessTest extends BaseTestCase
{
public function testGlobalAccess(): void
{
$service = $this->getInjectableFactory()->create(SettingsService::class);
$data = $service->getConfigData();
$hiddenProps = [
'googleMapsApiKey',
'outboundEmailFromAddress',
'jobPeriod',
'cryptKey',
];
foreach ($hiddenProps as $prop) {
$this->assertObjectNotHasProperty($prop, $data);
}
$this->assertObjectHasProperty('cacheTimestamp', $data);
}
public function testUserAccess1(): void
{
$this->setConfigParams([
'internalSmtpServer' => 'test',
'internalSmtpPassword' => 'test',
'oAuthServer' => [
'test' => 'test',
],
]);
$this->createUser('tester', [
'data' => [
'Email' => [
'create' => 'yes',
'read' => 'team',
'edit' => 'team',
'delete' => 'no'
]
]
]);
$this->authenticate('tester');
$service = $this->getInjectableFactory()->create(SettingsService::class);
$data = $service->getConfigData();
$hiddenProps = [
'googleMapsApiKey',
'outboundEmailFromAddress',
'jobPeriod',
'cryptKey',
'oAuthServer',
'apiExposeExceptions',
'database',
'internalSmtpServer',
'internalSmtpPassword',
];
foreach ($hiddenProps as $prop) {
$this->assertObjectNotHasProperty($prop, $data);
}
$this->assertObjectHasProperty('version', $data);
}
public function testUserAccess2()
{
$this->createUser('tester', [
'data' => [
'Email' => false
]
]);
$this->authenticate('tester');
$data = $this->getInjectableFactory()
->create(SettingsService::class)
->getConfigData();
$this->assertFalse(property_exists($data, 'outboundEmailFromAddress'));
}
public function testAdminAccess()
{
$this->createUser([
'userName' => 'admin-tester',
'type' => 'admin',
]);
$this->authenticate('admin-tester');
$data = $this->getInjectableFactory()
->create(SettingsService::class)
->getConfigData();
$this->assertTrue(property_exists($data, 'version'));
$this->assertTrue(property_exists($data, 'outboundEmailFromAddress'));
$this->assertTrue(property_exists($data, 'jobPeriod'));
$this->assertFalse(property_exists($data, 'cryptKey'));
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testReadOnly(): void
{
$this->createUser([
'userName' => 'admin-tester',
'type' => User::TYPE_ADMIN,
]);
$this->authenticate('admin-tester');
$service = $this->getInjectableFactory()->create(SettingsService::class);
$service->setConfigData((object) [
'systemUserId' => 'test',
'cryptKey' => 'test',
'oAuthServerCryptKey' => 'test',
]);
$this->reCreateApplication(reuse: true);
$this->assertNull($this->getConfig()->get('systemUserId'));
$this->assertNotEquals('test', $this->getConfig()->get('cryptKey'));
$this->assertNotEquals('test', $this->getConfig()->get('oAuthServerCryptKey'));
}
}

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\integration\Espo\Stream;
use Espo\Core\Acl\Table;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Record\UpdateParams;
use Espo\Core\Select\SearchParams;
use Espo\Entities\Note;
use Espo\Modules\Crm\Entities\KnowledgeBaseArticle;
use Espo\Tools\Stream\RecordService;
use tests\integration\Core\BaseTestCase;
class AuditTest extends BaseTestCase
{
public function testAudit1(): void
{
$this->createUser('test1', [
'auditPermission' => Table::LEVEL_YES,
'data' => [
KnowledgeBaseArticle::ENTITY_TYPE => [
'create' => Table::LEVEL_NO,
'read' => Table::LEVEL_ALL,
],
]
]);
$this->createUser('test2', [
'auditPermission' => Table::LEVEL_NO,
'data' => [
KnowledgeBaseArticle::ENTITY_TYPE => [
'create' => Table::LEVEL_NO,
'read' => Table::LEVEL_ALL,
],
]
]);
$this->createUser('test3', [
'auditPermission' => Table::LEVEL_NO,
'data' => [
KnowledgeBaseArticle::ENTITY_TYPE => [
'create' => Table::LEVEL_NO,
'read' => Table::LEVEL_NO,
],
]
]);
$this->getMetadata()->set('entityDefs', KnowledgeBaseArticle::ENTITY_TYPE, [
'fields' => [
'publishDate' => [
'audited' => true,
],
],
]);
$this->getMetadata()->save();
$service = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(KnowledgeBaseArticle::class);
/** @noinspection PhpUnhandledExceptionInspection */
$article = $service->create((object) [
'name' => 'Test 1',
'publishDate' => '2025-01-01',
], CreateParams::create())->getEntity();
/** @noinspection PhpUnhandledExceptionInspection */
$service->update($article->getId(), (object) [
'publishDate' => '2025-01-02',
], UpdateParams::create());
$em = $this->getEntityManager();
$note = $em->getRDBRepositoryByClass(Note::class)
->where([
'parentId' => $article->getId(),
'parentType' => $article->getEntityType(),
'type' => Note::TYPE_UPDATE,
])
->findOne();
$this->assertNotNull($note);
$this->assertEquals(['publishDate'], $note->getData()?->fields);
$searchParams = SearchParams::create();
$this->authenticate('test1');
$service = $this->getInjectableFactory()->create(RecordService::class);
/** @noinspection PhpUnhandledExceptionInspection */
$recordCollection = $service->findUpdates(KnowledgeBaseArticle::ENTITY_TYPE, $article->getId(), $searchParams);
$this->assertCount(1, $recordCollection->getCollection()->getValueMapList());
$this->authenticate('test2');
$isThrown = false;
try {
$service = $this->getInjectableFactory()->create(RecordService::class);
/** @noinspection PhpUnhandledExceptionInspection */
$service->findUpdates(KnowledgeBaseArticle::ENTITY_TYPE, $article->getId(), $searchParams);
} catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
$this->authenticate('test3');
$isThrown = false;
try {
$service = $this->getInjectableFactory()->create(RecordService::class);
/** @noinspection PhpUnhandledExceptionInspection */
$service->findUpdates(KnowledgeBaseArticle::ENTITY_TYPE, $article->getId(), $searchParams);
} catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
}
}

View File

@@ -0,0 +1,135 @@
<?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 integration\Espo\Stream;
use Espo\Core\Acl\Table;
use Espo\Core\Field\LinkMultiple;
use Espo\Core\Name\Field;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\Tools\EntityManager\EntityManager;
use Espo\Tools\Stream\Service;
use tests\integration\Core\BaseTestCase;
class FollowTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testFollow(): void
{
$roleData = [
Account::ENTITY_TYPE => [
'create' => Table::LEVEL_NO,
'read' => Table::LEVEL_ALL,
'stream' => Table::LEVEL_ALL,
],
Contact::ENTITY_TYPE => [
'create' => Table::LEVEL_NO,
'read' => Table::LEVEL_ALL,
'stream' => Table::LEVEL_ALL,
],
Meeting::ENTITY_TYPE => [
'create' => Table::LEVEL_NO,
'read' => Table::LEVEL_ALL,
'stream' => Table::LEVEL_ALL,
],
];
$user1 = $this->createUser('test-1', [
'data' => $roleData,
]);
$user2 = $this->createUser('test-2', [
'data' => $roleData,
]);
$user3 = $this->createUser('test-3', [
'data' => $roleData,
]);
$tool = $this->getInjectableFactory()->create(EntityManager::class);
/** @noinspection PhpArrayKeyDoesNotMatchArrayShapeInspection */
$tool->update(Contact::ENTITY_TYPE, ['assignedUsers' => true]);
$this->getDataManager()->rebuildMetadata();
$this->reCreateApplication(reuse: true);
$streamService = $this->getInjectableFactory()->create(Service::class);
$em = $this->getEntityManager();
//
$account = $em->getRepositoryByClass(Account::class)->getNew();
$account->setAssignedUser($user1);
$em->saveEntity($account);
$this->assertTrue($streamService->checkIsFollowed($account, $user1->getId()));
$account->setAssignedUser($user2);
$em->saveEntity($account);
$this->assertTrue($streamService->checkIsFollowed($account, $user2->getId()));
//
$contact = $em->getRepositoryByClass(Contact::class)->getNew();
$contact->setLinkMultipleIdList(Field::ASSIGNED_USERS, [$user1->getId()]);
$em->saveEntity($contact);
$this->assertTrue($streamService->checkIsFollowed($contact, $user1->getId()));
$contact->setLinkMultipleIdList(Field::ASSIGNED_USERS, [$user2->getId()]);
$em->saveEntity($contact);
$this->assertTrue($streamService->checkIsFollowed($contact, $user2->getId()));
//
$meeting = $em->getRepositoryByClass(Meeting::class)->getNew();
$meeting->setAssignedUser($user1);
$meeting->setUsers(LinkMultiple::create()->withAddedId($user2->getId()));
$em->saveEntity($meeting);
$this->assertTrue($streamService->checkIsFollowed($meeting, $user1->getId()));
$this->assertTrue($streamService->checkIsFollowed($meeting, $user2->getId()));
$meeting->setUsers(LinkMultiple::create()->withAddedId($user3->getId()));
$em->saveEntity($meeting);
$this->assertTrue($streamService->checkIsFollowed($meeting, $user3->getId()));
}
}

View File

@@ -0,0 +1,197 @@
<?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\integration\Espo\Stream;
use Espo\Core\Acl\Table;
use Espo\Core\Api\Request;
use Espo\Core\Exceptions\Forbidden;
use Espo\Entities\Note;
use Espo\Modules\Crm\Entities\Account;
use Espo\Tools\Stream\Api\DeleteMyReactions;
use Espo\Tools\Stream\Api\PostMyReactions;
use Espo\Tools\Stream\MassNotePreparator;
use tests\integration\Core\BaseTestCase;
class ReactionsTest extends BaseTestCase
{
private const REACTION_LIKE = 'Like';
public function testReactions(): void
{
$userTest = $this->createUser('test', [
'data' => [
Account::ENTITY_TYPE => [
'create' => Table::LEVEL_NO,
'read' => Table::LEVEL_ALL,
'stream' => Table::LEVEL_OWN,
],
]
]);
$em = $this->getEntityManager();
$account1 = $em->getRDBRepositoryByClass(Account::class)->getNew();
$account1->setAssignedUser($userTest);
$em->saveEntity($account1);
$account2 = $em->getRDBRepositoryByClass(Account::class)->getNew();
$em->saveEntity($account2);
$note1 = $this->getEntityManager()->getRDBRepositoryByClass(Note::class)->getNew();
$note1
->setType(Note::TYPE_POST)
->setParent($account1)
->setPost('Test');
$em->saveEntity($note1);
$note2 = $this->getEntityManager()->getRDBRepositoryByClass(Note::class)->getNew();
$note2
->setType(Note::TYPE_POST)
->setParent($account2)
->setPost('Test');
$em->saveEntity($note2);
$this->authenticate('test');
// Has access.
$request = $this->createMock(Request::class);
$request->expects($this->any())
->method('getRouteParam')
->willReturnMap([
['id', $note1->getId()],
['type', self::REACTION_LIKE],
]);
/** @noinspection PhpUnhandledExceptionInspection */
$this->getInjectableFactory()
->create(PostMyReactions::class)
->process($request);
$this->getInjectableFactory()->create(MassNotePreparator::class)->prepare([$note1]);
$this->assertEquals([self::REACTION_LIKE], $note1->get('myReactions'));
$this->assertEquals(1, $note1->get('reactionCounts')->{self::REACTION_LIKE});
// Un-react.
$request = $this->createMock(Request::class);
$request->expects($this->any())
->method('getRouteParam')
->willReturnMap([
['id', $note1->getId()],
['type', self::REACTION_LIKE],
]);
/** @noinspection PhpUnhandledExceptionInspection */
$this->getInjectableFactory()
->create(DeleteMyReactions::class)
->process($request);
$this->getInjectableFactory()->create(MassNotePreparator::class)->prepare([$note1]);
$this->assertEquals([], $note1->get('myReactions'));
$this->assertEquals(0, $note1->get('reactionCounts')->{self::REACTION_LIKE} ?? 0);
// No access to note.
$isThrown = false;
try {
$request = $this->createMock(Request::class);
$request->expects($this->any())
->method('getRouteParam')
->willReturnMap([
['id', $note2->getId()],
['type', self::REACTION_LIKE],
]);
/** @noinspection PhpUnhandledExceptionInspection */
$this->getInjectableFactory()
->create(PostMyReactions::class)
->process($request);
} catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
// No access to note to un-react.
$isThrown = false;
try {
$request = $this->createMock(Request::class);
$request->expects($this->any())
->method('getRouteParam')
->willReturnMap([
['id', $note2->getId()],
['type', self::REACTION_LIKE],
]);
/** @noinspection PhpUnhandledExceptionInspection */
$this->getInjectableFactory()
->create(DeleteMyReactions::class)
->process($request);
} catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
// Not allowed reaction.
$isThrown = false;
try {
$request = $this->createMock(Request::class);
$request->expects($this->any())
->method('getRouteParam')
->willReturnMap([
['id', $note1->getId()],
['type', 'Smile'],
]);
/** @noinspection PhpUnhandledExceptionInspection */
$this->getInjectableFactory()
->create(PostMyReactions::class)
->process($request);
} catch (Forbidden) {
$isThrown = true;
}
$this->assertTrue($isThrown);
}
}

View File

@@ -0,0 +1,116 @@
<?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\integration\Espo\Stream;
use Espo\Core\Acl\Table;
use Espo\Core\ORM\Repository\Option\SaveOption;
use Espo\Core\Select\SearchParams;
use Espo\Entities\Note;
use Espo\Entities\Portal;
use Espo\Modules\Crm\Entities\CaseObj;
use Espo\Tools\Stream\RecordService;
use tests\integration\Core\BaseTestCase;
class RecordTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testPortal(): void
{
$em = $this->getEntityManager();
$portal = $em->createEntity(Portal::ENTITY_TYPE, ['name' => 'Test']);
$portalUser = $this->createUser([
'userName' => 'test',
'portalsIds' => [$portal->getId()],
], [
'data' => [
CaseObj::ENTITY_TYPE => [
Table::ACTION_READ => Table::LEVEL_OWN,
Table::ACTION_STREAM => Table::LEVEL_OWN,
]
],
], isPortal: true);
$case = $em->createEntity(CaseObj::ENTITY_TYPE, [
'name' => 'Test',
], [SaveOption::CREATED_BY_ID => $portalUser->getId(), SaveOption::SILENT => true]);
$note = $em->getRDBRepositoryByClass(Note::class)->getNew();
$note->setParent($case);
$note->setType(Note::TYPE_POST);
$em->saveEntity($note);
$notePinned = $em->getRDBRepositoryByClass(Note::class)->getNew();
$notePinned->setParent($case);
$notePinned->setType(Note::TYPE_POST);
$notePinned->setIsPinned(true);
$em->saveEntity($notePinned);
$noteInternal = $em->getRDBRepositoryByClass(Note::class)->getNew();
$noteInternal->setParent($case);
$noteInternal->setType(Note::TYPE_POST);
$noteInternal->setIsInternal(true);
$em->saveEntity($noteInternal);
$noteInternalPinned = $em->getRDBRepositoryByClass(Note::class)->getNew();
$noteInternalPinned->setParent($case);
$noteInternalPinned->setType(Note::TYPE_POST);
$noteInternalPinned->setIsPinned(true);
$noteInternalPinned->setIsInternal(true);
$em->saveEntity($noteInternalPinned);
$this->auth(
userName: 'test',
portalId: $portal->getId(),
);
$app = $this->createApplication(
portalId: $portal->getId(),
reuse: true,
);
$this->setApplication($app);
$service = $this->getInjectableFactory()->create(RecordService::class);
$collection = $service->find(CaseObj::ENTITY_TYPE, $case->getId(), SearchParams::create())->getCollection();
$pinnedCollection = $service->getPinned(CaseObj::ENTITY_TYPE, $case->getId());
$this->assertCount(2, $collection);
$this->assertEquals($note->getId(), $collection[1]->getId());
$this->assertEquals($notePinned->getId(), $collection[0]->getId());
$this->assertCount(1, $pinnedCollection);
$this->assertEquals($notePinned->getId(), $collection[0]->getId());
}
}

View File

@@ -0,0 +1,80 @@
<?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\integration\Espo\Tools\Activities;
use Espo\Core\Api\Request;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\CaseObj;
use Espo\Modules\Crm\Tools\Activities\Api\GetComposeAddressList;
use tests\integration\Core\BaseTestCase;
class ComposeEmailAddressListTest extends BaseTestCase
{
public function testGetGetAddressList(): void
{
$em = $this->getEntityManager();
$account = $em->createEntity(Account::ENTITY_TYPE, [
'name' => 'Test',
'emailAddress' => 'test@test.com',
]);
$case = $em->createEntity(CaseObj::ENTITY_TYPE, [
'accountId' => $account->getId(),
]);
$action = $this->getInjectableFactory()
->create(GetComposeAddressList::class);
$request = $this->createMock(Request::class);
$request
->expects($this->any())
->method('getRouteParam')
->willReturnMap([
['parentType', CaseObj::ENTITY_TYPE],
['id', $case->getId()]
]);
/** @noinspection PhpUnhandledExceptionInspection */
$response = $action->process($request);
$list = json_decode($response->getBody());
$this->assertEquals([
(object) [
'emailAddress' => 'test@test.com',
'name' => $account->get('name'),
'entityId' => $account->getId(),
'entityType' => Account::ENTITY_TYPE,
]
], $list);
}
}

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\integration\Espo\Tools\App;
use Espo\Tools\App\LanguageService;
use tests\integration\Core\BaseTestCase;
class LanguageTest extends BaseTestCase
{
public function testAclDependency(): void
{
$this->createUser('tester', [
'data' => [
'Lead' => false,
'Opportunity' => [
'create' => 'no',
'read' => 'own',
'edit' => 'no',
'delete' => 'no',
'stream' => 'no',
],
]
]);
$this->authenticate('tester');
$data = $this->getInjectableFactory()->create(LanguageService::class)->getDataForFrontend();
$data = json_decode(json_encode($data));
$this->assertNotNull($data?->Lead?->options?->source);
$this->assertNull($data?->Lead?->options?->status ?? null);
}
}

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\integration\Espo\Tools\App;
use Espo\Core\Utils\Metadata;
use Espo\Tools\App\MetadataService;
use tests\integration\Core\BaseTestCase;
class MetadataTest extends BaseTestCase
{
public function testAclDependency(): void
{
$metadata = $this->getContainer()->getByClass(Metadata::class);
$metadata->set('app', 'metadata', [
'aclDependencies' => [
'entityDefs.Campaign' => [
'anyScopeList' => ['Opportunity'],
],
],
]);
$metadata->save();
$this->createUser('tester', [
'data' => [
'Lead' => false,
'Opportunity' => [
'create' => 'no',
'read' => 'own',
'edit' => 'no',
'delete' => 'no',
'stream' => 'no',
],
]
]);
$this->authenticate('tester');
$data = $this->getInjectableFactory()->create(MetadataService::class)->getDataForFrontend();
$this->assertIsArray($data?->entityDefs?->Lead?->fields?->source?->options);
$this->assertNull($data->entityDefs->Lead->fields->name ?? null);
$this->assertNotNull($data?->entityDefs->Campaign ?? null);
}
}

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\integration\Espo\Tools\AppSecret;
use Espo\Core\Utils\Crypt;
use Espo\Entities\AppSecret;
use Espo\Tools\AppSecret\SecretProvider;
use tests\integration\Core\BaseTestCase;
class SecretProviderTest extends BaseTestCase
{
public function testGet(): void
{
$em = $this->getEntityManager();
$provider = $this->getInjectableFactory()->create(SecretProvider::class);
$secret = $em->getRDBRepositoryByClass(AppSecret::class)->getNew();
$crypt = $this->getInjectableFactory()->create(Crypt::class);
$value = 'hello';
$secret
->setName('test')
->setSecretValue($crypt->encrypt($value));
$em->saveEntity($secret);
$this->assertEquals(
$value,
$provider->get('test')
);
$this->assertNull($provider->get('Test'));
}
}

View File

@@ -0,0 +1,243 @@
<?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\integration\Espo\Tools\DynamicLogic;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Tools\EntityManager\EntityManager;
use Espo\Tools\FieldManager\FieldManager;
use Espo\Tools\LinkManager\LinkManager;
use tests\integration\Core\BaseTestCase;
class CascadingFieldsTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testLinkMultiple(): void
{
$emTool = $this->getInjectableFactory()->create(EntityManager::class);
$lmTool = $this->getInjectableFactory()->create(LinkManager::class);
$fmTool = $this->getInjectableFactory()->create(FieldManager::class);
$emTool->create('MyTest', 'Base');
$lmTool->create([
'link' => 'accounts',
'linkForeign' => 'tests',
'linkType' => 'manyToMany',
'entity' => 'CMyTest',
'entityForeign' => Account::ENTITY_TYPE,
'label' => 'Account',
'labelForeign' => 'Test',
'relationName' => 'testAccount',
'linkMultipleField' => true,
'linkMultipleFieldForeign' => true,
]);
$lmTool->create([
'link' => 'contacts',
'linkForeign' => 'tests',
'linkType' => 'manyToMany',
'entity' => 'CMyTest',
'entityForeign' => Contact::ENTITY_TYPE,
'label' => 'Contact',
'labelForeign' => 'Test',
'relationName' => 'testContact',
'linkMultipleField' => true,
'linkMultipleFieldForeign' => true,
]);
$fmTool->update('CMyTest', 'contacts', [
'dynamicLogicCascading' => [
'items' => [
[
'localField' => 'accounts',
'foreignField' => 'accounts',
'matchRequired' => true,
]
]
]
]);
$this->reCreateApplication();
$em = $this->getEntityManager();
$a1 = $em->createEntity(Account::ENTITY_TYPE);
$a2 = $em->createEntity(Account::ENTITY_TYPE);
$c1A12 = $em->createEntity(Contact::ENTITY_TYPE, [
'accountsIds' => [$a1->getId(), $a2->getId()],
'accountId' => $a1->getId(),
]);
$c2A1 = $em->createEntity(Contact::ENTITY_TYPE, [
'accountsIds' => [$a1->getId()],
'accountId' => $a1->getId(),
]);
$service = $this->getContainer()->getByClass(ServiceContainer::class)->get('CMyTest');
//
$service->create((object) [
'name' => 't1',
'accountsIds' => [$a1->getId()],
'contactsIds' => [$c2A1->getId()],
], CreateParams::create());
//
$service->create((object) [
'name' => 't1',
'accountsIds' => [$a1->getId(), $a2->getId()],
'contactsIds' => [$c1A12->getId()],
], CreateParams::create());
//
$thrown = false;
try {
$service->create((object) [
'name' => 't1',
'accountsIds' => [$a1->getId(), $a2->getId()],
'contactsIds' => [$c2A1->getId()],
], CreateParams::create());
} catch (BadRequest) {
$thrown = true;
}
$this->assertTrue($thrown);
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testLink(): void
{
$emTool = $this->getInjectableFactory()->create(EntityManager::class);
$lmTool = $this->getInjectableFactory()->create(LinkManager::class);
$fmTool = $this->getInjectableFactory()->create(FieldManager::class);
$emTool->create('MyTest', 'Base');
$lmTool->create([
'link' => 'account',
'linkForeign' => 'tests',
'linkType' => 'manyToOne',
'entity' => 'CMyTest',
'entityForeign' => Account::ENTITY_TYPE,
'label' => 'Account',
'labelForeign' => 'Test',
'linkMultipleField' => true,
'linkMultipleFieldForeign' => true,
]);
$lmTool->create([
'link' => 'contact',
'linkForeign' => 'tests',
'linkType' => 'manyToOne',
'entity' => 'CMyTest',
'entityForeign' => Contact::ENTITY_TYPE,
'label' => 'Contact',
'labelForeign' => 'Test',
'linkMultipleField' => true,
'linkMultipleFieldForeign' => true,
]);
$fmTool->update('CMyTest', 'contact', [
'dynamicLogicCascading' => [
'items' => [
[
'localField' => 'account',
'foreignField' => 'accounts',
'matchRequired' => true,
]
]
]
]);
$this->reCreateApplication();
$em = $this->getEntityManager();
$a1 = $em->createEntity(Account::ENTITY_TYPE);
$a2 = $em->createEntity(Account::ENTITY_TYPE);
$c1A12 = $em->createEntity(Contact::ENTITY_TYPE, [
'accountsIds' => [$a1->getId(), $a2->getId()],
'accountId' => $a1->getId(),
]);
$c2A1 = $em->createEntity(Contact::ENTITY_TYPE, [
'accountsIds' => [$a1->getId()],
'accountId' => $a1->getId(),
]);
$service = $this->getContainer()->getByClass(ServiceContainer::class)->get('CMyTest');
//
$service->create((object) [
'name' => 't1',
'accountId' => $a1->getId(),
'contactId' => $c2A1->getId(),
], CreateParams::create());
//
$service->create((object) [
'name' => 't1',
'accountId' => $a2->getId(),
'contactId' => $c1A12->getId(),
], CreateParams::create());
//
$thrown = false;
try {
$service->create((object) [
'name' => 't1',
'accountId' => $a2->getId(),
'contactId' => $c2A1->getId(),
], CreateParams::create());
} catch (BadRequest) {
$thrown = true;
}
$this->assertTrue($thrown);
}
}

View File

@@ -0,0 +1,235 @@
<?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 integration\Espo\Tools\EmailTemplate;
use DateTimeImmutable;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingProcessor;
use Espo\Core\Field\Link;
use Espo\Core\Field\LinkMultiple;
use Espo\Core\Utils\DateTime\Clock;
use Espo\Entities\EmailTemplate;
use Espo\Entities\Team;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Tools\EmailTemplate\Data as TemplateData;
use Espo\Tools\EmailTemplate\Params as TemplateParams;
use Espo\Tools\EmailTemplate\Processor;
use tests\integration\Core\BaseTestCase;
class ProcessorTest extends BaseTestCase
{
public function testProcessAccess(): void
{
$em = $this->getEntityManager();
$team = $em->getRDBRepositoryByClass(Team::class)->getNew();
$team
->setName('Team 1');
$em->saveEntity($team);
$user = $em->getRDBRepositoryByClass(User::class)->getNew();
$user
->setFirstName('Test')
->setLastName('Hello')
->setUserName('test')
->setDefaultTeam(Link::fromEntity($team))
->setTeams(LinkMultiple::create()->withAddedId($team->getId()));
$em->saveEntity($user);
$template1 = $em->getRDBRepositoryByClass(EmailTemplate::class)->getNew();
$template1->setMultiple([
'subject' => '{Lead.name} {Lead.assignedUser.name} {Lead.assignedUser.password}',
'body' => '{Lead.name} {Lead.assignedUser.name} {Lead.assignedUser.password}',
]);
$em->saveEntity($template1);
$template2 = $em->getRDBRepositoryByClass(EmailTemplate::class)->getNew();
$template2->setMultiple([
'subject' => '{{name}} {{assignedUser.name}} {{assignedUser.password}}',
'body' => '{{name}} {{assignedUser.name}} {{assignedUser.password}}',
]);
$em->saveEntity($template2);
$template3 = $em->getRDBRepositoryByClass(EmailTemplate::class)->getNew();
$template3->setMultiple([
'subject' => '{{name}} {{password}} {{defaultTeam.name}}',
'body' => '{User.name} {User.password} {User.defaultTeam.name}',
]);
$em->saveEntity($template3);
$template4 = $em->getRDBRepositoryByClass(EmailTemplate::class)->getNew();
$template4->setMultiple([
'subject' => '{{name}} {{password}} {{defaultTeam.name}}',
'body' => '{User.name} {User.password} {User.defaultTeam.name}',
]);
$em->saveEntity($template4);
$lead = $em->getRDBRepositoryByClass(Lead::class)->getNew();
$lead
->setFirstName('Lead')
->setLastName('Abc')
->setAssignedUser($user);
$em->saveEntity($lead);
$processor = $this->getInjectableFactory()->create(Processor::class);
$params = TemplateParams::create()
->withApplyAcl(false);
$data = TemplateData::create();
//
$emailData1 = $processor->process($template1, $params, $data->withParent($lead));
$this->assertEquals(
'Lead Abc Test Hello {Lead.assignedUser.password}',
$emailData1->getSubject(),
);
$this->assertEquals(
'Lead Abc Test Hello {Lead.assignedUser.password}',
$emailData1->getBody(),
);
//
$emailData2 = $processor->process($template2, $params, $data->withParent($lead));
$this->assertEquals(
'Lead Abc Test Hello ',
$emailData2->getSubject(),
);
$this->assertEquals(
'Lead Abc Test Hello ',
$emailData2->getBody(),
);
//
$emailData3 = $processor->process($template3, $params, $data->withParent($user));
$this->assertEquals(
'Test Hello ',
$emailData3->getSubject(),
);
$this->assertEquals(
'Test Hello {User.password} {User.defaultTeam.name}',
$emailData3->getBody(),
);
}
public function testProcessAndCleanup(): void
{
$this->setConfigParams([
'dateFormat' => 'MM/DD/YYYY',
]);
$clock = $this->createMock(Clock::class);
$clock->method('now')
->willReturn(new DateTimeImmutable('2030-01-02 00:00'));
$this->setApplication(
$this->createApplication(
binding: new class ($clock) implements BindingProcessor {
public function __construct(private Clock $clock) {}
public function process(Binder $binder): void
{
$binder->bindInstance(Clock::class, $this->clock);
}
},
reuse: true,
)
);
//
$em = $this->getEntityManager();
$account = $em->getRDBRepositoryByClass(Account::class)->getNew();
$account->setName('Account 1');
$em->saveEntity($account);
$lead = $em->getRDBRepositoryByClass(Lead::class)->getNew();
$lead->setFirstName('One');
$em->saveEntity($lead);
$em->refreshEntity($lead);
$template1 = $em->getRDBRepositoryByClass(EmailTemplate::class)->getNew();
$template1->setMultiple([
'subject' => 'Test {Person.firstName} test {Account.name}',
'body' => 'Test {Person.name} test {Account.name} {today}.',
]);
$em->saveEntity($template1);
//
$processor = $this->getInjectableFactory()->create(Processor::class);
$params = TemplateParams::create()
->withApplyAcl(false);
$data = TemplateData::create();
//
$emailData1 = $processor->process($template1, $params, $data->withParent($account));
$this->assertEquals(
'Test test Account 1',
$emailData1->getSubject(),
);
$this->assertEquals(
'Test test Account 1 01/02/2030.',
$emailData1->getBody(),
);
//
$emailData2 = $processor->process($template1, $params, $data->withParent($lead));
$this->assertEquals(
'Test One test {Account.name}',
$emailData2->getSubject(),
);
$this->assertEquals(
'Test One test {Account.name} 01/02/2030.',
$emailData2->getBody(),
);
}
}

View File

@@ -0,0 +1,80 @@
<?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\integration\Espo\Tools\EntityTypeManager;
use Espo\Core\Templates\Entities\BasePlus;
use Espo\Tools\EntityManager\EntityManager;
use integration\Core\NoTransaction;
use tests\integration\Core\BaseTestCase;
#[NoTransaction]
class EntityTypeManagerTest extends BaseTestCase
{
private const string PARAM_CATEGORIES = 'categories';
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testCreateUpdateDelete(): void
{
$name = 'TestEntityType';
$tool = $this->getInjectableFactory()->create(EntityManager::class);
$tool->create($name, BasePlus::TEMPLATE_TYPE, [
'stream' => true,
]);
//
$tool->update('C' . $name, [
'stream' => false,
self::PARAM_CATEGORIES => true,
]);
$this->reCreateApplication();
$em = $this->getEntityManager();
$this->assertTrue($em->hasRepository("C$name"));
$this->assertTrue($em->hasRepository("C{$name}Category"));
//
$tool->delete('C' . $name);
$this->reCreateApplication();
$em = $this->getEntityManager();
$this->assertFalse($em->hasRepository("C{$name}Category"));
$this->assertFalse($em->hasRepository("C$name"));
}
}

View File

@@ -0,0 +1,257 @@
<?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 integration\Espo\Tools\Import;
use Espo\Core\Acl\Table;
use Espo\Entities\Attachment;
use Espo\Entities\Role;
use Espo\Entities\User;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Tools\Import\Import;
use Espo\Tools\Import\Params;
use tests\integration\Core\BaseTestCase;
class ImportTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testRelateOnImport(): void
{
$metadata = $this->getMetadata();
$metadata->set('entityDefs', Contact::ENTITY_TYPE, [
'fields' => [
'accountType' => [
'relateOnImport' => true,
]
],
]);
$metadata->save();
//
$this->createUser('test', [
Role::FIELD_DATA => [
'Import' => true,
Account::ENTITY_TYPE => [
Table::ACTION_READ => Table::LEVEL_ALL,
],
Contact::ENTITY_TYPE => [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_ALL,
Table::ACTION_EDIT => Table::LEVEL_ALL,
],
],
]);
$this->authenticate('test');
$em = $this->getEntityManager();
//
$attachment = $em->getRDBRepositoryByClass(Attachment::class)->getNew();
$attachment
->setName('test.csv')
->setType('text/csv')
->setRole(Import::FILE_ROLE)
->setContents(
<<<'EOT'
lastName,accountType
Test,Partner
```
EOT
);
$em->saveEntity($attachment);
$account = $em->getRDBRepositoryByClass(Account::class)->getNew();
$account->setType(Account::TYPE_PARTNER);
$em->saveEntity($account);
$import = $this->getInjectableFactory()->create(Import::class);
$import
->setUser($this->getContainer()->getByClass(User::class))
->setEntityType(Contact::ENTITY_TYPE)
->setParams(
Params::create()
)
->setAttachmentId($attachment->getId())
->setAttributeList(['lastName', 'accountType']);
$import->run();
$contact = $em->getRDBRepositoryByClass(Contact::class)
->where(['lastName' => 'Test'])
->findOne();
$this->assertNotNull($contact);
$this->assertEquals($account->getId(), $contact->getAccount()?->getId());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testRelateByForeignName(): void
{
$this->createUser('test', [
Role::FIELD_DATA => [
'Import' => true,
Account::ENTITY_TYPE => [
Table::ACTION_READ => Table::LEVEL_ALL,
],
Contact::ENTITY_TYPE => [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_ALL,
Table::ACTION_EDIT => Table::LEVEL_ALL,
],
],
]);
$this->authenticate('test');
$em = $this->getEntityManager();
//
$attachment = $em->getRDBRepositoryByClass(Attachment::class)->getNew();
$attachment
->setName('test.csv')
->setType('text/csv')
->setRole(Import::FILE_ROLE)
->setContents(
<<<'EOT'
lastName,accountName
Test,Hello
```
EOT
);
$em->saveEntity($attachment);
$account = $em->getRDBRepositoryByClass(Account::class)->getNew();
$account->setName('Hello');
$em->saveEntity($account);
$import = $this->getInjectableFactory()->create(Import::class);
$import
->setEntityType(Contact::ENTITY_TYPE)
->setParams(
Params::create()
)
->setAttachmentId($attachment->getId())
->setAttributeList(['lastName', 'accountName']);
$import->run();
$contact = $em->getRDBRepositoryByClass(Contact::class)
->where(['lastName' => 'Test'])
->findOne();
$this->assertNotNull($contact);
$this->assertEquals($account->getId(), $contact->getAccount()?->getId());
}
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testDefaultValues(): void
{
$this->createUser('test', [
Role::FIELD_DATA => [
'Import' => true,
Account::ENTITY_TYPE => [
Table::ACTION_CREATE => Table::LEVEL_YES,
Table::ACTION_READ => Table::LEVEL_ALL,
],
],
Role::FIELD_FIELD_DATA => [
Account::ENTITY_TYPE => [
'type' => [
Table::ACTION_READ => Table::LEVEL_YES,
Table::ACTION_EDIT => Table::LEVEL_NO,
],
]
]
]);
$this->authenticate('test');
$em = $this->getEntityManager();
//
$attachment = $em->getRDBRepositoryByClass(Attachment::class)->getNew();
$attachment
->setName('test.csv')
->setType('text/csv')
->setRole(Import::FILE_ROLE)
->setContents(
<<<'EOT'
name
Test
```
EOT
);
$em->saveEntity($attachment);
$account = $em->getRDBRepositoryByClass(Account::class)->getNew();
$account->setType(Account::TYPE_PARTNER);
$em->saveEntity($account);
$import = $this->getInjectableFactory()->create(Import::class);
$import
->setEntityType(Account::ENTITY_TYPE)
->setParams(
Params::create()->withDefaultValues([
'type' => Account::TYPE_PARTNER,
'description' => 'Test.',
])
)
->setAttachmentId($attachment->getId())
->setAttributeList(['name']);
$import->run();
//
$account = $em->getRDBRepositoryByClass(Account::class)
->where(['name' => 'Test'])
->findOne();
$this->assertNotNull($account);
$this->assertNull($account->getType());
$this->assertEquals('Test.', $account->getDescription());
}
}

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\integration\Espo\Tools\Lock;
use Espo\Core\Action\ActionFactory;
use Espo\Core\Action\Data;
use Espo\Core\Action\Params;
use Espo\Core\Name\Field;
use Espo\Modules\Crm\Entities\Account;
use Espo\ORM\Exceptions\ValidationException;
use tests\integration\Core\BaseTestCase;
class LockTest extends BaseTestCase
{
/** @noinspection PhpUnhandledExceptionInspection */
public function testLock1(): void
{
$em = $this->getEntityManager();
$account = $em->getRDBRepositoryByClass(Account::class)->getNew();
$account->setName('Test');
$em->saveEntity($account);
$account->set(Field::IS_LOCKED, true);
$em->saveEntity($account);
$this->expectException(ValidationException::class);
$account->setName('Test 1');
$em->saveEntity($account);
//
$actionFactory = $this->getInjectableFactory()->create(ActionFactory::class);
$actionFactory->create('Unlock', Account::ENTITY_TYPE)->process(
params: new Params(
entityType: Account::ENTITY_TYPE,
id: $account->getId(),
),
data: Data::fromRaw((object) [])
);
$em->refreshEntity($account);
$this->assertFalse($account->get(Field::IS_LOCKED));
//
$actionFactory->create('Lock', Account::ENTITY_TYPE)->process(
params: new Params(
entityType: Account::ENTITY_TYPE,
id: $account->getId(),
),
data: Data::fromRaw((object) [])
);
$em->refreshEntity($account);
$this->assertTrue($account->get(Field::IS_LOCKED));
}
}

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\integration\Espo\Tools\Merge;
use Espo\Core\Action\Actions\Merge\Merger;
use Espo\Core\Action\Params;
use Espo\Core\Field\DateTimeOptional;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Modules\Crm\Entities\Meeting;
use tests\integration\Core\BaseTestCase;
class MergeTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testMerge(): void
{
$em = $this->getEntityManager();
$lead1 = $em->getRepositoryByClass(Lead::class)->getNew();
$lead1->setLastName('L 1');
$em->saveEntity($lead1);
$lead2 = $em->getRepositoryByClass(Lead::class)->getNew();
$lead2->setLastName('L 2');
$em->saveEntity($lead2);
$meeting = $em->getRepositoryByClass(Meeting::class)->getNew();
$meeting->setParent($lead2);
$meeting->setName('M');
$meeting->setDateStart(DateTimeOptional::createNow());
$em->saveEntity($meeting);
$merger = $this->getInjectableFactory()->create(Merger::class);
$merger->process(
params: new Params(Lead::ENTITY_TYPE, $lead1->getId()),
sourceIdList: [$lead2->getId()],
data: (object) [],
);
$em->refreshEntity($meeting);
$this->assertEquals($lead1->getId(), $meeting->getParent()?->getId());
$this->assertEquals(Lead::ENTITY_TYPE, $meeting->getParent()?->getEntityType());
$this->assertNull(
$em->getEntityById(Lead::ENTITY_TYPE, $lead2->getId())
);
}
}

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\integration\Espo\Tools\Notification;
use Espo\Core\Select\SearchParams;
use Espo\Entities\User;
use Espo\Tools\Notification\RecordService;
use tests\integration\Core\BaseTestCase;
class RecordServiceTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testGet(): void
{
$this->createUser('tester');
$this->authenticate('tester');
$service = $this->getInjectableFactory()->create(RecordService::class);
$user = $this->getContainer()->getByClass(User::class);
$service->get($user, SearchParams::create());
$this->assertTrue(true);
}
}

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