| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Unit\Service\Shop;
- use App\ApiResources\Shop\NewStructureArtistPremiumTrialRequest;
- use App\Entity\Organization\Organization;
- use App\Entity\Organization\Settings;
- use App\Entity\Shop\ShopRequest;
- use App\Enum\Organization\LegalEnum;
- use App\Enum\Organization\PrincipalTypeEnum;
- use App\Enum\Organization\SettingsProductEnum;
- use App\Enum\Shop\ShopRequestStatus;
- use App\Enum\Shop\ShopRequestType;
- use App\Message\Message\Shop\NewStructureArtistPremiumTrial;
- use App\Service\Dolibarr\DolibarrApiService;
- use App\Service\Dolibarr\DolibarrUtils;
- use App\Service\Mailer\Mailer;
- use App\Service\Organization\OrganizationFactory;
- use App\Service\Shop\ShopService;
- use App\Service\Utils\DatesUtils;
- use Doctrine\ORM\EntityManagerInterface;
- use libphonenumber\PhoneNumberUtil;
- use PHPUnit\Framework\MockObject\MockObject;
- use PHPUnit\Framework\TestCase;
- use Psr\Log\LoggerInterface;
- use Symfony\Component\Messenger\Envelope;
- use Symfony\Component\Messenger\MessageBusInterface;
- use Symfony\Component\Serializer\SerializerInterface;
- class TestableShopService extends ShopService
- {
- public PhoneNumberUtil $phoneNumberUtil;
- public function controlShopRequestData(ShopRequestType $type, array $data): void
- {
- parent::controlShopRequestData($type, $data);
- }
- public function createRequest(ShopRequestType $type, array $data): ShopRequest
- {
- return parent::createRequest($type, $data);
- }
- public function sendRequestValidationLink(ShopRequest $shopRequest): void
- {
- parent::sendRequestValidationLink($shopRequest);
- }
- public function startArtistPremiumTrial(Organization $organization, NewStructureArtistPremiumTrialRequest $request): void
- {
- parent::startArtistPremiumTrial($organization, $request);
- }
- public function handleNewStructureArtistPremiumTrialRequest(string $token): void
- {
- parent::handleNewStructureArtistPremiumTrialRequest($token);
- }
- public function createOrganization(NewStructureArtistPremiumTrialRequest $trialRequest): Organization
- {
- return parent::createOrganization($trialRequest);
- }
- public function generateSubdomain(string $name): string
- {
- return parent::generateSubdomain($name);
- }
- public function validateNewStructureArtistPremiumTrialRequest($request): void
- {
- parent::validateNewStructureArtistPremiumTrialRequest($request);
- }
- }
- /**
- * Unit tests for ShopService
- */
- class ShopServiceTest extends TestCase
- {
- private readonly MockObject|EntityManagerInterface $entityManager;
- private readonly MockObject|Mailer $mailer;
- private string $publicBaseUrl;
- private readonly MockObject|OrganizationFactory $organizationFactory;
- private readonly MockObject|SerializerInterface $serializer;
- private readonly MockObject|LoggerInterface $logger;
- private readonly MockObject|DolibarrApiService $dolibarrApiService;
- private readonly MockObject|DolibarrUtils $dolibarrUtils;
- private readonly MockObject|MessageBusInterface $messageBus;
- public function setUp(): void
- {
- $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
- $this->mailer = $this->getMockBuilder(Mailer::class)->disableOriginalConstructor()->getMock();
- $this->publicBaseUrl = 'https://example.com';
- $this->organizationFactory = $this->getMockBuilder(OrganizationFactory::class)->disableOriginalConstructor()->getMock();
- $this->serializer = $this->getMockBuilder(SerializerInterface::class)->disableOriginalConstructor()->getMock();
- $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
- $this->dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)->disableOriginalConstructor()->getMock();
- $this->dolibarrUtils = $this->getMockBuilder(DolibarrUtils::class)->disableOriginalConstructor()->getMock();
- $this->messageBus = $this->getMockBuilder(MessageBusInterface::class)->disableOriginalConstructor()->getMock();
- }
- private function getShopServiceMockFor(string $methodName): TestableShopService|MockObject
- {
- $shopService = $this
- ->getMockBuilder(TestableShopService::class)
- ->setConstructorArgs(
- [
- $this->entityManager,
- $this->mailer,
- $this->publicBaseUrl,
- $this->organizationFactory,
- $this->serializer,
- $this->logger,
- $this->dolibarrApiService,
- $this->dolibarrUtils,
- $this->messageBus,
- ]
- )
- ->setMethodsExcept([$methodName])
- ->getMock();
- return $shopService;
- }
- public function tearDown(): void
- {
- DatesUtils::clearFakeDatetime();
- }
- /**
- * Test registerNewShopRequest method
- */
- public function testRegisterNewShopRequest(): void
- {
- $shopService = $this->getShopServiceMockFor('registerNewShopRequest');
- $type = ShopRequestType::NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL;
- $data = [
- 'representativeEmail' => 'test@example.com',
- 'representativeFirstName' => 'John',
- 'representativeLastName' => 'Doe',
- 'structureName' => 'Test Structure',
- ];
- $shopRequest = $this->getMockBuilder(ShopRequest::class)->getMock();
- $shopService->expects(self::once())
- ->method('controlShopRequestData')
- ->with($type, $data);
- $shopService->expects(self::once())
- ->method('createRequest')
- ->with($type, $data)
- ->willReturn($shopRequest);
- $shopService->expects(self::once())
- ->method('sendRequestValidationLink')
- ->with($shopRequest);
- $result = $shopService->registerNewShopRequest($type, $data);
- $this->assertSame($shopRequest, $result);
- }
- /**
- * Test controlShopRequestData method for NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL type
- */
- public function testControlShopRequestDataForNewStructureArtistPremiumTrial(): void
- {
- $shopService = $this->getShopServiceMockFor('controlShopRequestData');
- $type = ShopRequestType::NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL;
- $data = [
- 'representativeEmail' => 'test@example.com',
- 'representativeFirstName' => 'John',
- 'representativeLastName' => 'Doe',
- 'structureName' => 'Test Structure',
- ];
- $shopService->expects(self::once())
- ->method('validateNewStructureArtistPremiumTrialRequest')
- ->with($data);
- $shopService->controlShopRequestData($type, $data);
- }
- /**
- * Test processShopRequest method for NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL type
- */
- public function testProcessShopRequest(): void
- {
- $shopService = $this->getShopServiceMockFor('processShopRequest');
- $shopRequest = $this->getMockBuilder(ShopRequest::class)->getMock();
- $shopRequest->method('getType')->willReturn(ShopRequestType::NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL);
- $shopRequest->method('getToken')->willReturn('test-token');
- $this->messageBus
- ->expects(self::once())
- ->method('dispatch')
- ->with(self::callback(function ($message) {
- return $message instanceof NewStructureArtistPremiumTrial
- && $message->getToken() === 'test-token';
- }))
- ->willReturn(new Envelope(new NewStructureArtistPremiumTrial('azerty')));
- $shopRequest
- ->expects(self::once())
- ->method('setStatus')
- ->with(ShopRequestStatus::VALIDATED);
- $this->entityManager->expects(self::once())
- ->method('persist')
- ->with($shopRequest);
- $this->entityManager->expects(self::once())
- ->method('flush');
- $shopService->processShopRequest($shopRequest);
- }
- /**
- * Test createRequest method
- */
- public function testCreateRequest(): void
- {
- $shopService = $this->getShopServiceMockFor('createRequest');
- $uuidRx = "/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i";
- $type = ShopRequestType::NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL;
- $data = [
- 'representativeEmail' => 'test@example.com',
- 'representativeFirstName' => 'John',
- 'representativeLastName' => 'Doe',
- 'structureName' => 'Test Structure',
- ];
- $this->entityManager->expects(self::once())
- ->method('persist')
- ->with(self::callback(function ($shopRequest) use ($type, $data, $uuidRx) {
- return $shopRequest instanceof ShopRequest
- && $shopRequest->getType() === $type
- && $shopRequest->getData() === $data
- && preg_match($uuidRx, $shopRequest->getToken());
- }));
- $this->entityManager->expects(self::once())
- ->method('flush');
- $result = $shopService->createRequest($type, $data);
- $this->assertInstanceOf(ShopRequest::class, $result);
- $this->assertSame($type, $result->getType());
- $this->assertSame($data, $result->getData());
- $this->assertMatchesRegularExpression($uuidRx, $result->getToken());
- }
- /**
- * Test sendRequestValidationLink method
- */
- public function testSendRequestValidationLink(): void
- {
- $shopService = $this->getShopServiceMockFor('sendRequestValidationLink');
- $token = 'test-token';
- $data = [
- 'representativeEmail' => 'test@example.com',
- 'representativeFirstName' => 'John',
- 'representativeLastName' => 'Doe',
- 'structureName' => 'Test Structure',
- ];
- $shopRequest = $this->getMockBuilder(ShopRequest::class)->getMock();
- $shopRequest->method('getToken')->willReturn($token);
- $shopRequest->method('getData')->willReturn($data);
- $this->mailer
- ->expects(self::once())
- ->method('main')
- ->with(self::callback(function ($model) use ($token, $data) {
- var_dump($model);
- return $model->getToken() === $token
- && $model->getRepresentativeEmail() === $data['representativeEmail']
- && $model->getRepresentativeFirstName() === $data['representativeFirstName']
- && $model->getRepresentativeLastName() === $data['representativeLastName']
- && $model->getStructureName() === $data['structureName']
- && $model->getValidationUrl() === 'https://example.com/api/public/shop/validate/' . $token;
- }));
- $shopRequest->expects(self::once())
- ->method('setStatus')
- ->with(ShopRequestStatus::ACTIVATION_LINK_SENT);
- $this->entityManager->expects(self::once())
- ->method('persist')
- ->with($shopRequest);
- $this->entityManager->expects(self::once())
- ->method('flush');
- $shopService->sendRequestValidationLink($shopRequest);
- }
- /**
- * Test startArtistPremiumTrial method
- */
- public function testStartArtistPremiumTrial(): void
- {
- $shopService = $this->getShopServiceMockFor('startArtistPremiumTrial');
- DatesUtils::setFakeDatetime('2025-01-01 12:00:00');
- $organization = $this->getMockBuilder(Organization::class)->getMock();
- $settings = $this->getMockBuilder(Settings::class)->getMock();
- $settings->method('getProduct')->willReturn(SettingsProductEnum::FREEMIUM);
- $organization->method('getSettings')->willReturn($settings);
- $organization->method('getId')->willReturn(123);
- $request = $this->getMockBuilder(NewStructureArtistPremiumTrialRequest::class)->getMock();
- $request->method('getRepresentativeFirstName')->willReturn('John');
- $request->method('getRepresentativeLastName')->willReturn('Doe');
- $request->method('getRepresentativeFunction')->willReturn('Manager');
- $request->method('getRepresentativeEmail')->willReturn('test@example.com');
- $request->method('getRepresentativePhone')->willReturn('+33123456789');
- $settings
- ->expects(self::once())
- ->method('setProductBeforeTrial')
- ->with(SettingsProductEnum::FREEMIUM);
- $settings
- ->expects(self::once())
- ->method('setTrialActive')
- ->with(true);
- $settings
- ->expects(self::once())
- ->method('setLastTrialStartDate')
- ->with(self::callback(function ($dateTime) {
- return $dateTime instanceof \DateTime && $dateTime->format('Y-m-d H:i:s') === '2025-01-01 12:00:00';
- }));
- $settings->expects(self::once())
- ->method('setProduct')
- ->with(SettingsProductEnum::ARTIST_PREMIUM);
- $this->entityManager->expects(self::once())
- ->method('persist')
- ->with($settings);
- $this->entityManager->expects(self::once())
- ->method('flush');
- $this->dolibarrApiService->expects(self::once())
- ->method('getSocietyId')
- ->with(123)
- ->willReturn(456);
- $this->dolibarrUtils->expects(self::once())
- ->method('getProductId')
- ->with(SettingsProductEnum::ARTIST_PREMIUM, true)
- ->willReturn(789);
- $this->dolibarrApiService->expects(self::once())
- ->method('createContract')
- ->with(456, 789, true, 1)
- ->willReturn(101112);
- $this->dolibarrApiService->expects(self::once())
- ->method('createContractLine')
- ->with(101112, 789, 1);
- $this->dolibarrUtils->expects(self::once())
- ->method('updateSocietyCommercialsWithApi')
- ->with(456);
- $this->dolibarrUtils->expects(self::once())
- ->method('getDolibarrProductName')
- ->with(SettingsProductEnum::ARTIST_PREMIUM, true)
- ->willReturn('Artist Premium (essai)');
- $this->dolibarrApiService->expects(self::once())
- ->method('updateSocietyProduct')
- ->with(456, 'Artist Premium (essai)');
- $this->dolibarrUtils->expects(self::once())
- ->method('addActionComm')
- ->with(456, "Ouverture de la période d'essai", self::callback(function ($message) {
- return strpos($message, 'John') !== false
- && strpos($message, 'Doe') !== false
- && strpos($message, 'Manager') !== false
- && strpos($message, 'test@example.com') !== false
- && strpos($message, '+33123456789') !== false;
- }));
- $shopService->startArtistPremiumTrial($organization, $request);
- // Clear the fake date time after the test
- DatesUtils::clearFakeDatetime();
- }
- /**
- * Test handleNewStructureArtistPremiumTrialRequest method
- */
- public function testHandleNewStructureArtistPremiumTrialRequest(): void
- {
- $shopService = $this->getShopServiceMockFor('handleNewStructureArtistPremiumTrialRequest');
- $token = 'test-token';
- $data = [
- 'representativeEmail' => 'test@example.com',
- 'representativeFirstName' => 'John',
- 'representativeLastName' => 'Doe',
- 'structureName' => 'Test Structure',
- ];
- $shopRequest = $this->getMockBuilder(ShopRequest::class)->getMock();
- $shopRequest->method('getToken')->willReturn($token);
- $shopRequest->method('getData')->willReturn($data);
- $this->entityManager
- ->expects(self::once())
- ->method('find')
- ->with(ShopRequest::class, $token)
- ->willReturn($shopRequest);
- $trialRequest = $this->getMockBuilder(\App\ApiResources\Shop\NewStructureArtistPremiumTrialRequest::class)
- ->getMock();
- $this->serializer
- ->expects(self::once())
- ->method('deserialize')
- ->with(json_encode($data), NewStructureArtistPremiumTrialRequest::class, 'json')
- ->willReturn($trialRequest);
- $organization = $this->getMockBuilder(Organization::class)->getMock();
- $shopService
- ->expects(self::once())
- ->method('createOrganization')
- ->with($trialRequest)
- ->willReturn($organization);
- $shopService
- ->expects(self::once())
- ->method('startArtistPremiumTrial')
- ->with($organization, $trialRequest);
- $this->logger->expects(self::once())
- ->method('info')
- ->with('Successfully processed NewStructureArtistPremiumTrial for token: ' . $token);
- $shopService->handleNewStructureArtistPremiumTrialRequest($token);
- }
- /**
- * Test handleNewStructureArtistPremiumTrialRequest method with non-existent token
- */
- public function testHandleNewStructureArtistPremiumTrialRequestWithNonExistentToken(): void
- {
- $shopService = $this->getShopServiceMockFor('handleNewStructureArtistPremiumTrialRequest');
- $this->entityManager->expects(self::once())
- ->method('find')
- ->with(ShopRequest::class, 'non-existent-token')
- ->willReturn(null);
- $this->logger->expects(self::once())
- ->method('error')
- ->with('Cannot find ShopRequest with token: non-existent-token');
- $shopService->handleNewStructureArtistPremiumTrialRequest('non-existent-token');
- }
- /**
- * Test createOrganization method
- */
- public function testCreateOrganization(): void
- {
- $shopService = $this->getShopServiceMockFor('createOrganization');
- $trialRequest = $this
- ->getMockBuilder(NewStructureArtistPremiumTrialRequest::class)
- ->getMock();
- $trialRequest->method('getStructureName')->willReturn('Test Structure');
- $trialRequest->method('getAddress')->willReturn('123 Main St');
- $trialRequest->method('getAddressComplement')->willReturn('Suite 456');
- $trialRequest->method('getPostalCode')->willReturn('75000');
- $trialRequest->method('getCity')->willReturn('Paris');
- $trialRequest->method('getStructureEmail')->willReturn('structure@example.com');
- $trialRequest->method('getStructureType')->willReturn(PrincipalTypeEnum::ARTISTIC_EDUCATION_ONLY);
- $trialRequest->method('getLegalStatus')->willReturn(LegalEnum::ASSOCIATION_LAW_1901);
- $trialRequest->method('getSiren')->willReturn('123456789');
- $trialRequest->method('getRepresentativePhone')->willReturn('+33123456789');
- $shopService
- ->expects(self::once())
- ->method('generateSubdomain')
- ->with('Test Structure')
- ->willReturn('test-structure');
- $organization = $this->getMockBuilder(Organization::class)->getMock();
- // Mock the organizationFactory.create method to return the mock Organization
- $this->organizationFactory
- ->expects(self::once())
- ->method('create')
- ->with(self::callback(function ($organizationCreationRequest) {
- return $organizationCreationRequest->getName() === 'Test Structure'
- && $organizationCreationRequest->getStreetAddress1() === '123 Main St'
- && $organizationCreationRequest->getStreetAddress2() === 'Suite 456'
- && $organizationCreationRequest->getPostalCode() === '75000'
- && $organizationCreationRequest->getCity() === 'Paris'
- && $organizationCreationRequest->getEmail() === 'structure@example.com'
- && $organizationCreationRequest->getPrincipalType() === PrincipalTypeEnum::ARTISTIC_EDUCATION_ONLY
- && $organizationCreationRequest->getLegalStatus() === LegalEnum::ASSOCIATION_LAW_1901
- && $organizationCreationRequest->getSiretNumber() === '123456789'
- && $organizationCreationRequest->getPhoneNumber() === '+33123456789'
- && $organizationCreationRequest->getSubdomain() === 'test-structure'
- && $organizationCreationRequest->getProduct()->value === SettingsProductEnum::FREEMIUM->value
- && $organizationCreationRequest->getCreateWebsite() === false
- && $organizationCreationRequest->isClient() === false;
- }))
- ->willReturn($organization);
- $result = $shopService->createOrganization($trialRequest);
- $this->assertSame($organization, $result);
- }
- /**
- * Test generateSubdomain method
- */
- public function testGenerateSubdomain(): void
- {
- $shopService = $this->getShopServiceMockFor('generateSubdomain');
- // Test with a simple name
- $this->assertEquals('test-structure', $shopService->generateSubdomain('Test Structure'));
- // Test with accents and special characters
- $this->assertEquals('ecole-de-musique', $shopService->generateSubdomain('École de Musique'));
- // Test with multiple spaces and special characters
- $this->assertEquals('conservatoire-regional', $shopService->generateSubdomain('Conservatoire Régional!@#'));
- // Test with a very long name (should be truncated to 30 characters)
- $longName = 'This is a very long name that should be truncated';
- $this->assertEquals('this-is-a-very-long-name-that', $shopService->generateSubdomain($longName));
- // Test with leading and trailing hyphens (should be trimmed)
- $this->assertEquals('trimmed', $shopService->generateSubdomain('--Trimmed--'));
- // Test with consecutive special characters (should be replaced with a single hyphen)
- $this->assertEquals('multiple-hyphens', $shopService->generateSubdomain('Multiple!!!Hyphens'));
- }
- /**
- * Test validateNewStructureArtistPremiumTrialRequest method with valid data
- */
- public function testValidateNewStructureArtistPremiumTrialRequest(): void
- {
- $shopService = $this->getShopServiceMockFor('validateNewStructureArtistPremiumTrialRequest');
- $phoneNumberUtil = $this
- ->getMockBuilder(PhoneNumberUtil::class)
- ->disableOriginalConstructor()
- ->getMock();
- $shopService->phoneNumberUtil = $phoneNumberUtil;
- $request = new NewStructureArtistPremiumTrialRequest();
- $request->setStructureName('Test Structure');
- $request->setCity('Test City');
- $request->setPostalCode('12345');
- $request->setAddress('Test Address');
- $request->setAddressComplement('Test Address Complement');
- $request->setRepresentativePhone('+33123456789');
- $phoneNumberUtil->expects(self::once())
- ->method('isPossibleNumber')
- ->with('+33123456789')
- ->willReturn(true);
- $this->organizationFactory->expects(self::once())
- ->method('interruptIfOrganizationExists')
- ->with(self::callback(function ($organizationCreationRequest) {
- return $organizationCreationRequest->getName() === 'Test Structure'
- && $organizationCreationRequest->getCity() === 'Test City'
- && $organizationCreationRequest->getPostalCode() === '12345'
- && $organizationCreationRequest->getStreetAddress1() === 'Test Address'
- && $organizationCreationRequest->getStreetAddress2() === 'Test Address Complement'
- && $organizationCreationRequest->getStreetAddress3() === '';
- }));
- $shopService->validateNewStructureArtistPremiumTrialRequest($request);
- }
- /**
- * Test validateNewStructureArtistPremiumTrialRequest method with invalid phone number
- */
- public function testValidateNewStructureArtistPremiumTrialRequestWithInvalidPhoneNumber(): void
- {
- $shopService = $this->getShopServiceMockFor('validateNewStructureArtistPremiumTrialRequest');
- $phoneNumberUtil = $this->getMockBuilder(PhoneNumberUtil::class)
- ->disableOriginalConstructor()
- ->getMock();
- $shopService->phoneNumberUtil = $phoneNumberUtil;
- $request = new NewStructureArtistPremiumTrialRequest();
- $request->setRepresentativePhone('invalid-phone');
- // Mock the phoneNumberUtil to return false for isPossibleNumber
- $phoneNumberUtil->expects(self::once())
- ->method('isPossibleNumber')
- ->with('invalid-phone')
- ->willReturn(false);
- $this->expectException(\RuntimeException::class);
- $this->expectExceptionMessage('Invalid phone number');
- $shopService->validateNewStructureArtistPremiumTrialRequest($request);
- }
- }
|