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,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);
}
}

File diff suppressed because it is too large Load Diff

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 integration\Espo\Tools\OAuthServer;
use Espo\Core\Field\DateTime;
use Espo\Core\Utils\DateTime\Clock;
use Espo\Tools\OAuthServer\Cleanup\GeneralCleanup;
use Espo\Tools\OAuthServer\Entities\AccessToken;
use Espo\Tools\OAuthServer\Entities\AuthorizationCode;
use Espo\Tools\OAuthServer\Entities\RefreshToken;
use tests\integration\Core\BaseTestCase;
class CleanupTest extends BaseTestCase
{
public function testCleanupAuthTokens(): void
{
$now = DateTime::fromString('2030-01-01 00:00');
$this->reCreateApplicationWithNow($now);
$em = $this->getEntityManager();
$accessToken1 = $em->getRDBRepositoryByClass(AccessToken::class)->getNew();
$accessToken1->setExpiresAt($now->addHours(1));
$em->saveEntity($accessToken1);
$accessToken2 = $em->getRDBRepositoryByClass(AccessToken::class)->getNew();
$accessToken2->setExpiresAt($now->addHours(1));
$em->saveEntity($accessToken2);
$refreshToken = $em->getRDBRepositoryByClass(RefreshToken::class)->getNew();
$refreshToken
->setAccessToken($accessToken2)
->setExpiresAt($now->addMonths(3));
$em->saveEntity($refreshToken);
//
$cleanup = $this->getInjectableFactory()->create(GeneralCleanup::class);
$cleanup->process();
$this->assertNotNull(
$em->getRDBRepositoryByClass(AccessToken::class)->getById($accessToken1->getId())
);
$this->assertNotNull(
$em->getRDBRepositoryByClass(AccessToken::class)->getById($accessToken2->getId())
);
//
$now = $now->addDays(15)->addHours(1);
$this->reCreateApplicationWithNow($now);
//
$cleanup = $this->getInjectableFactory()->create(GeneralCleanup::class);
$cleanup->process();
$this->assertFalse(
$em->getRDBRepositoryByClass(AccessToken::class)->getById($accessToken1->getId()) !== null
);
$this->assertTrue(
$em->getRDBRepositoryByClass(AccessToken::class)->getById($accessToken2->getId()) !== null
);
//
$refreshToken->setRevoked();
$em->saveEntity($refreshToken);
//
$cleanup->process();
$this->assertFalse(
$em->getRDBRepositoryByClass(AccessToken::class)->getById($accessToken2->getId()) !== null
);
}
public function testCleanupRefreshTokens(): void
{
$now = DateTime::fromString('2030-01-01 00:00');
$this->reCreateApplicationWithNow($now);
$em = $this->getEntityManager();
$refreshToken = $em->getRDBRepositoryByClass(RefreshToken::class)->getNew();
$refreshToken
->setExpiresAt($now->addMonths(1));
$em->saveEntity($refreshToken);
//
$cleanup = $this->getInjectableFactory()->create(GeneralCleanup::class);
$cleanup->process();
$this->assertNotNull(
$em->getRDBRepositoryByClass(RefreshToken::class)->getById($refreshToken->getId())
);
//
$now = $now->addMonths(1)->addDays(15);
$this->reCreateApplicationWithNow($now);
//
$cleanup = $this->getInjectableFactory()->create(GeneralCleanup::class);
$cleanup->process();
$this->assertFalse(
$em->getRDBRepositoryByClass(RefreshToken::class)->getById($refreshToken->getId()) !== null
);
}
public function testCleanupAuthorizationCode(): void
{
$now = DateTime::fromString('2030-01-01 00:00');
$this->reCreateApplicationWithNow($now);
$em = $this->getEntityManager();
$code = $em->getRDBRepositoryByClass(AuthorizationCode::class)->getNew();
$code
->setExpiresAt($now->addHours(1));
$em->saveEntity($code);
//
$cleanup = $this->getInjectableFactory()->create(GeneralCleanup::class);
$cleanup->process();
$this->assertNotNull(
$em->getRDBRepositoryByClass(AuthorizationCode::class)->getById($code->getId())
);
//
$now = $now->addHours(1)->addDays(15);
$this->reCreateApplicationWithNow($now);
//
$cleanup = $this->getInjectableFactory()->create(GeneralCleanup::class);
$cleanup->process();
$this->assertFalse(
$em->getRDBRepositoryByClass(AuthorizationCode::class)->getById($code->getId()) !== null
);
}
private function reCreateApplicationWithNow(DateTime $now): void
{
$clock = $this->createMock(Clock::class);
$clock->expects(self::any())
->method('now')
->willReturn($now->toDateTime());
$this->reCreateApplication(
reuse: true,
binding: $this->prepareBinding(function ($binder) use ($clock) {
$binder->bindInstance(Clock::class, $clock);
}),
);
}
}

View File

@@ -0,0 +1,167 @@
<?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\OAuthServer;
use Espo\Core\Acl\Scope;
use Espo\Core\Field\DateTime;
use Espo\Tools\OAuthServer\ClientType;
use Espo\Tools\OAuthServer\ConnectedApp\ConnectedAppService;
use Espo\Tools\OAuthServer\Entities\AccessToken;
use Espo\Tools\OAuthServer\Entities\Client;
use Espo\Tools\OAuthServer\Entities\RefreshToken;
use Espo\Tools\OAuthServer\Repository\AccessTokenRepository;
use tests\integration\Core\BaseTestCase;
class ConnectedAppTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testListAndDisconnect(): void
{
$em = $this->getEntityManager();
$user1 = $this->createUser('test-1');
$user2 = $this->createUser('test-2');
$client1 = $this->createClient('Test 1');
$client2 = $this->createClient('Test 2');
//
$em->saveEntity(
$em->getRDBRepositoryByClass(RefreshToken::class)
->getNew()
->setClient($client1)
->setExpiresAt(DateTime::createNow()->addHours(1))
->setUser($user1)
);
$em->saveEntity(
$em->getRDBRepositoryByClass(RefreshToken::class)
->getNew()
->setClient($client2)
->setExpiresAt(DateTime::createNow()->addHours(1))
->setUser($user1)
);
$em->saveEntity(
$em->getRDBRepositoryByClass(RefreshToken::class)
->getNew()
->setClient($client1)
->setExpiresAt(DateTime::createNow()->addHours(1))
->setUser($user1)
);
$em->saveEntity(
$em->getRDBRepositoryByClass(AccessToken::class)
->getNew()
->setClient($client1)
->setExpiresAt(DateTime::createNow()->addHours(1))
->setUser($user1)
);
$em->saveEntity(
$em->getRDBRepositoryByClass(AccessToken::class)
->getNew()
->setClient($client2)
->setExpiresAt(DateTime::createNow()->addHours(1))
->setUser($user1)
);
$em->saveEntity(
$em->getRDBRepositoryByClass(RefreshToken::class)
->getNew()
->setClient($client1)
->setExpiresAt(DateTime::createNow()->addHours(1))
->setUser($user2)
);
//
$accessTokenRepo = $this->getInjectableFactory()->create(AccessTokenRepository::class);
$service = $this->getInjectableFactory()->create(ConnectedAppService::class);
$apps = $service->getList($user1);
$this->assertCount(2, $apps);
$this->assertEquals($client1->getIdentifier(), $apps[0]->id);
$this->assertEquals(
1,
$accessTokenRepo
->findActiveForClientIdAndUser($client1->getId(), $user1->getId())
->count(),
);
//
$service->disconnect($user1, $client1->getIdentifier());
$apps = $service->getList($user1);
$this->assertCount(1, $apps);
$this->assertEquals(
0,
$accessTokenRepo
->findActiveForClientIdAndUser($client1->getId(), $user1->getId())
->count(),
);
//
$apps = $service->getList($user2);
$this->assertCount(1, $apps);
//
$apps = $service->getList($user2);
$this->assertCount(1, $apps);
}
private function createClient(string $name): Client
{
$em = $this->getEntityManager();
$client = $em->getRDBRepositoryByClass(Client::class)->getNew();
$client
->setName($name)
->setScopes([Scope::GLOBAL])
->setClientType(ClientType::Confidential)
->setRedirectUris(['http://localhost/oauth/callback']);
$em->saveEntity($client);
return $client;
}
}

View File

@@ -0,0 +1,153 @@
<?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\OAuthServer;
use Espo\Core\Field\Date;
use Espo\Core\Field\DateTime;
use Espo\Core\Utils\DateTime\Clock;
use Espo\Tools\OAuthServer\Entities\AccessToken;
use Espo\Tools\OAuthServer\Entities\AuthorizationCode;
use Espo\Tools\OAuthServer\Entities\Client;
use Espo\Tools\OAuthServer\Entities\ClientSecret;
use Espo\Tools\OAuthServer\Entities\RefreshToken;
use Espo\Tools\OAuthServer\Jobs\OAuthServerControl;
use tests\integration\Core\BaseTestCase;
class ExpirationTest extends BaseTestCase
{
public function testExpire(): void
{
$now = DateTime::fromString('2030-01-01 00:00');
$clock = $this->createClock($now);
$this->reCreateApplication(
reuse: true,
noUser: true,
binding: $this->prepareBinding(function ($binder) use ($clock) {
$binder->bindInstance(Clock::class, $clock);
}),
);
$em = $this->getEntityManager();
$client = $em->getRDBRepositoryByClass(Client::class)->getNew();
$em->saveEntity($client);
//
$expirationDate = Date::fromDateTime($now->toDateTime())->addMonths(1);
$expiresAt = DateTime::fromDateTime($now->toDateTime())->addMonths(1);
$secret = $em->getRDBRepositoryByClass(ClientSecret::class)->getNew();
$secret
->setClient($client)
->setExpirationDate($expirationDate);
$em->saveEntity($secret);
$code = $em->getRDBRepositoryByClass(AuthorizationCode::class)->getNew();
$code
->setClient($client)
->setExpiresAt($expiresAt);
$em->saveEntity($code);
$accessToken = $em->getRDBRepositoryByClass(AccessToken::class)->getNew();
$accessToken
->setClient($client)
->setExpiresAt($expiresAt);
$em->saveEntity($accessToken);
$refreshToken = $em->getRDBRepositoryByClass(RefreshToken::class)->getNew();
$refreshToken
->setClient($client)
->setExpiresAt($expiresAt);
$em->saveEntity($refreshToken);
//
$job = $this->getInjectableFactory()->create(OAuthServerControl::class);
$job->run();
$em->refreshEntity($secret);
$em->refreshEntity($code);
$em->refreshEntity($accessToken);
$em->refreshEntity($refreshToken);
$this->assertTrue($secret->isActive());
$this->assertTrue($code->isActive());
$this->assertTrue($accessToken->isActive());
$this->assertTrue($refreshToken->isActive());
//
$now = $now->addMonths(1);
$clock = $this->createClock($now);
$this->reCreateApplication(
reuse: true,
noUser: true,
binding: $this->prepareBinding(function ($binder) use ($clock) {
$binder->bindInstance(Clock::class, $clock);
}),
);
//
$job = $this->getInjectableFactory()->create(OAuthServerControl::class);
$job->run();
$em->refreshEntity($secret);
$em->refreshEntity($code);
$em->refreshEntity($accessToken);
$em->refreshEntity($refreshToken);
$this->assertTrue($secret->isExpired());
$this->assertFalse($secret->isActive());
$this->assertFalse($secret->isRevoked());
$this->assertTrue($code->isExpired());
$this->assertTrue($accessToken->isExpired());
$this->assertTrue($refreshToken->isExpired());
}
private function createClock(DateTime $now): Clock
{
$clock = $this->createMock(Clock::class);
$clock->expects(self::any())
->method('now')
->willReturn($now->toDateTime());
return $clock;
}
}

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\Tools\Object;
use Espo\Core\Name\Field;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\Modules\Crm\Entities\Opportunity;
use Espo\Modules\Crm\Entities\Task;
use Espo\Tools\Object\MetadataProvider;
use tests\integration\Core\BaseTestCase;
class MetadataProviderTest extends BaseTestCase
{
public function testGetLinks(): void
{
$provider = $this->getInjectableFactory()->create(MetadataProvider::class);
$this->assertEquals(Field::ACCOUNT, $provider->getAccountLink(Opportunity::ENTITY_TYPE));
$this->assertEquals(Field::ACCOUNT, $provider->getAccountLink(Meeting::ENTITY_TYPE));
$this->assertEquals(Field::PARENT, $provider->getParentLink(Meeting::ENTITY_TYPE));
$this->assertEquals(Field::PARENT, $provider->getParentLink(Task::ENTITY_TYPE));
}
}

View File

@@ -0,0 +1,237 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Tools\Pipeline;
use Espo\Classes\AppParams\Pipelines;
use Espo\Core\Acl\Table;
use Espo\Core\AclManager;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Field\Date;
use Espo\Core\Name\Field;
use Espo\Core\Record\CreateParams;
use Espo\Core\Record\DeleteParams;
use Espo\Core\Record\ServiceContainer;
use Espo\Core\Record\UpdateParams;
use Espo\Entities\Pipeline;
use Espo\Entities\PipelineStage;
use Espo\Entities\Role;
use Espo\Entities\Team;
use Espo\Modules\Crm\Entities\Opportunity;
use Espo\Tools\EntityManager\EntityManager as EntityManagerTool;
use integration\Core\NoTransaction;
use tests\integration\Core\BaseTestCase;
#[NoTransaction]
class PipelineTest extends BaseTestCase
{
/**
* @noinspection PhpUnhandledExceptionInspection
*/
public function testPipeline1(): void
{
$this->initPipeline(Opportunity::ENTITY_TYPE);
$em = $this->getEntityManager();
$role = $em->getRDBRepositoryByClass(Role::class)->getNew();
$role
->set(Field::NAME, 'Test')
->setRawData([
Opportunity::ENTITY_TYPE => [
Table::ACTION_EDIT => Table::LEVEL_TEAM,
],
]);
$em->saveEntity($role);
$team = $em->createEntity(Team::ENTITY_TYPE, [
Field::NAME => 'Test',
'rolesIds' => [$role->getId()],
]);
$user1 = $this->createUser([
'userName' => 'test-1',
'teamsIds' => [$team->getId()],
]);
$pipelineService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Pipeline::class);
$pipelineStageService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(PipelineStage::class);
$oppService = $this->getContainer()
->getByClass(ServiceContainer::class)
->getByClass(Opportunity::class);
//
$pipeline1 = $pipelineService->create((object) [
Field::NAME => 'Test 1',
Pipeline::FIELD_ENTITY_TYPE => Opportunity::ENTITY_TYPE,
Pipeline::FIELD_IS_AVAILABLE_FOR_ALL => false,
], CreateParams::create())->getEntity();
$this->assertCount(6, $pipeline1->getStages());
//
$pipeline2 = $pipelineService->create((object) [
Field::NAME => 'Test 2',
Pipeline::FIELD_ENTITY_TYPE => Opportunity::ENTITY_TYPE,
Pipeline::FIELD_IS_AVAILABLE_FOR_ALL => false,
Field::TEAMS . 'Ids' => [$team->getId()],
], CreateParams::create())->getEntity();
foreach ($pipeline2->getStages() as $stage) {
$pipelineStageService->delete($stage->getId(), DeleteParams::create());
}
$stage2_1 = $pipelineStageService->create((object) [
Field::NAME => 'Open',
PipelineStage::ATTR_PIPELINE_ID => $pipeline2->getId(),
PipelineStage::FIELD_MAPPED_STATUS => 'Prospecting',
], CreateParams::create())->getEntity();
$pipelineStageService->create((object) [
Field::NAME => 'Closed Won',
PipelineStage::ATTR_PIPELINE_ID => $pipeline2->getId(),
PipelineStage::FIELD_MAPPED_STATUS => Opportunity::STAGE_CLOSED_WON,
], CreateParams::create());
$pipelineStageService->create((object) [
Field::NAME => 'Closed Lost',
PipelineStage::ATTR_PIPELINE_ID => $pipeline2->getId(),
PipelineStage::FIELD_MAPPED_STATUS => Opportunity::STAGE_CLOSED_LOST,
], CreateParams::create());
$thrown = false;
try {
// Non-existing status.
$pipelineStageService->create((object) [
Field::NAME => 'Test',
PipelineStage::ATTR_PIPELINE_ID => $pipeline2->getId(),
PipelineStage::FIELD_MAPPED_STATUS => 'Test',
], CreateParams::create());
} catch (BadRequest) {
$thrown = true;
}
$this->assertTrue($thrown);
$em->refreshEntity($pipeline2);
$this->assertCount(3, $pipeline2->getStages());
//
$opp = $oppService->create((object) [
Field::NAME => 'Opp 1',
Field::PIPELINE . 'Id' => $pipeline1->getId(),
Field::PIPELINE_STAGE . 'Id' => $pipeline1->getStages()[0]->getId(),
Opportunity::FIELD_CLOSE_DATE => Date::fromString('2026-01-01')->toString(),
Opportunity::FIELD_AMOUNT => 1000,
], CreateParams::create())->getEntity();
$opp = $oppService->update($opp->getId(), (object) [
Field::PIPELINE . 'Id' => $pipeline2->getId(),
Field::PIPELINE_STAGE . 'Id' => $pipeline2->getStages()[0]->getId(),
], UpdateParams::create())->getEntity();
$this->assertEquals('Prospecting', $opp->getStage());
//
$opp = $oppService->create((object) [
Field::NAME => 'Opp 2',
Field::PIPELINE . 'Id' => $pipeline1->getId(),
Field::PIPELINE_STAGE . 'Id' => $pipeline1->getStages()[4]->getId(),
Opportunity::FIELD_CLOSE_DATE => Date::fromString('2026-01-01')->toString(),
Opportunity::FIELD_AMOUNT => 1000,
], CreateParams::create())->getEntity();
$this->assertEquals(Opportunity::STAGE_CLOSED_WON, $opp->getStage());
//
$opp = $oppService->create((object) [
Field::NAME => 'Opp 3',
Opportunity::FIELD_CLOSE_DATE => Date::fromString('2026-01-01')->toString(),
Opportunity::FIELD_AMOUNT => 1000,
], CreateParams::create());
$this->assertEquals($pipeline1->getId(), $opp->getEntity()->get(Field::PIPELINE . 'Id'));
$this->assertEquals($pipeline1->getStages()[0]->getId(), $opp->getEntity()->get(Field::PIPELINE_STAGE . 'Id'));
//
$pipelinesParam = $this->getInjectableFactory()->create(Pipelines::class);
$pipelines = $pipelinesParam->get();
$this->assertCount(2, $pipelines[Opportunity::ENTITY_TYPE]);
//
$this->auth($user1->getUserName());
$this->reCreateApplication(reuse: true);
$pipelinesParam = $this->getInjectableFactory()->create(Pipelines::class);
$pipelines = $pipelinesParam->get();
$this->assertCount(1, $pipelines[Opportunity::ENTITY_TYPE]);
//
$aclManager = $this->getContainer()->getByClass(AclManager::class);
$this->assertFalse($aclManager->checkEntityRead($user1, $pipeline1));
$this->assertTrue($aclManager->checkEntityRead($user1, $pipeline2));
$this->assertTrue($aclManager->checkEntityRead($user1, $stage2_1));
}
/**
* @noinspection PhpUnhandledExceptionInspection
* @noinspection PhpArrayKeyDoesNotMatchArrayShapeInspection
* @noinspection PhpSameParameterValueInspection
*/
private function initPipeline(string $entityType): void
{
$entityManager = $this->getInjectableFactory()->create(EntityManagerTool::class);
$entityManager->update($entityType, [
'pipelines' => true
]);
}
}

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\WorkingTime;
use Espo\Core\Field\DateTime;
use Espo\Core\Utils\Config\ConfigWriter;
use Espo\Entities\WorkingTimeCalendar;
use Espo\Tools\WorkingTime\CalendarUtilityFactory;
use tests\integration\Core\BaseTestCase;
class UtilityTest extends BaseTestCase
{
public function testUtilityUser(): void
{
$em = $this->getEntityManager();
$calendar = $em->createEntity(WorkingTimeCalendar::ENTITY_TYPE);
$user = $this->createUser('test');
$user->set('workingTimeCalendarId', $calendar->getId());
$em->saveEntity($user);
$utility = $this->getInjectableFactory()
->create(CalendarUtilityFactory::class)
->createForUser($user);
$this->assertTrue($utility->isWorkingDay(DateTime::fromString('2024-02-23 00:00')));
$this->assertFalse($utility->isWorkingDay(DateTime::fromString('2024-02-24 00:00')));
$this->assertFalse($utility->isWorkingDay(DateTime::fromString('2024-02-25 00:00')));
$this->assertTrue($utility->isWorkingDay(DateTime::fromString('2024-02-26 00:00')));
}
public function testUtilityGlobal(): void
{
$em = $this->getEntityManager();
$calendar = $em->createEntity(WorkingTimeCalendar::ENTITY_TYPE);
$configWriter = $this->getInjectableFactory()->create(ConfigWriter::class);
$configWriter->set('workingTimeCalendarId', $calendar->getId());
$configWriter->save();
$utility = $this->getInjectableFactory()
->create(CalendarUtilityFactory::class)
->createGlobal();
$this->assertTrue($utility->isWorkingDay(DateTime::fromString('2024-02-23 00:00')));
$this->assertFalse($utility->isWorkingDay(DateTime::fromString('2024-02-24 00:00')));
$this->assertFalse($utility->isWorkingDay(DateTime::fromString('2024-02-25 00:00')));
$this->assertTrue($utility->isWorkingDay(DateTime::fromString('2024-02-26 00:00')));
}
}