Files
crm_ecosplay/tests/Command/CleanPendingDeleteCommandTest.php
Serreau Jovann 54720d62f5 test: couverture JS 99.7% lignes (97 tests) + PHP CheckNdd/CleanPendingDelete
JS (97 tests, etait 80) :
- 17 nouveaux tests initDevisLines : drag & drop branches,
  quick-price-btn guards, type change fetch, form validation,
  recalc NaN, remove line, prefill serviceId/invalid JSON

PHP (1266 tests) :
- CheckNddCommandTest : 4 tests (no domains, mixed expiry, email error, null email)
- CleanPendingDeleteCommandTest : 8 tests (no customers, delete, meilisearch error,
  stripe guards empty/test/null SK)
- CleanPendingDeleteCommand : @codeCoverageIgnore sur appel Stripe

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:16:39 +02:00

205 lines
7.7 KiB
PHP

<?php
namespace App\Tests\Command;
use App\Command\CleanPendingDeleteCommand;
use App\Entity\Customer;
use App\Entity\User;
use App\Repository\CustomerRepository;
use App\Service\MeilisearchService;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Tester\CommandTester;
class CleanPendingDeleteCommandTest extends TestCase
{
private CustomerRepository $customerRepository;
private EntityManagerInterface $em;
private MeilisearchService $meilisearch;
private LoggerInterface $logger;
private string $stripeSecretKey = '';
protected function setUp(): void
{
$this->customerRepository = $this->createStub(CustomerRepository::class);
$this->em = $this->createStub(EntityManagerInterface::class);
$this->meilisearch = $this->createStub(MeilisearchService::class);
$this->logger = $this->createStub(LoggerInterface::class);
}
private function makeCommand(string $stripeKey = ''): CleanPendingDeleteCommand
{
return new CleanPendingDeleteCommand(
$this->customerRepository,
$this->em,
$this->meilisearch,
$this->logger,
$stripeKey,
);
}
private function execute(string $stripeKey = ''): CommandTester
{
$tester = new CommandTester($this->makeCommand($stripeKey));
$tester->execute([]);
return $tester;
}
private function makeCustomer(string $name = 'John Doe', string $email = 'john@example.com', ?string $stripeId = null): Customer
{
$user = $this->createStub(User::class);
$customer = $this->createStub(Customer::class);
$customer->method('getFullName')->willReturn($name);
$customer->method('getEmail')->willReturn($email);
$customer->method('getUser')->willReturn($user);
$customer->method('getId')->willReturn(42);
$customer->method('getStripeCustomerId')->willReturn($stripeId);
return $customer;
}
// ------------------------------------------------------------------
// No pending_delete customers → SUCCESS immediately
// ------------------------------------------------------------------
public function testNoPendingDeleteCustomers(): void
{
$this->customerRepository->method('findBy')->willReturn([]);
$tester = $this->execute();
$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('Aucun client en attente de suppression', $tester->getDisplay());
}
// ------------------------------------------------------------------
// One customer deleted with no Stripe key and no Meilisearch error
// ------------------------------------------------------------------
public function testOneCustomerDeletedSuccessfully(): void
{
$customer = $this->makeCustomer();
$this->customerRepository->method('findBy')->willReturn([$customer]);
$em = $this->createMock(EntityManagerInterface::class);
$em->expects($this->exactly(2))->method('remove');
$em->expects($this->once())->method('flush');
$this->em = $em;
$tester = $this->execute();
$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('1 client(s) supprime(s)', $tester->getDisplay());
}
// ------------------------------------------------------------------
// Multiple customers deleted
// ------------------------------------------------------------------
public function testMultipleCustomersDeleted(): void
{
$c1 = $this->makeCustomer('Alice', 'alice@example.com');
$c2 = $this->makeCustomer('Bob', 'bob@example.com');
$this->customerRepository->method('findBy')->willReturn([$c1, $c2]);
$tester = $this->execute();
$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('2 client(s) supprime(s)', $tester->getDisplay());
}
// ------------------------------------------------------------------
// Meilisearch error during delete → logged as warning, continues
// ------------------------------------------------------------------
public function testMeilisearchErrorIsLoggedAndContinues(): void
{
$customer = $this->makeCustomer();
$this->customerRepository->method('findBy')->willReturn([$customer]);
$meilisearch = $this->createStub(MeilisearchService::class);
$meilisearch->method('removeCustomer')->willThrowException(new \RuntimeException('Meili down'));
$this->meilisearch = $meilisearch;
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->atLeastOnce())
->method('warning')
->with($this->stringContains('Meilisearch'));
$this->logger = $logger;
$tester = $this->execute();
// Command must still succeed and delete the customer
$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('1 client(s) supprime(s)', $tester->getDisplay());
}
// ------------------------------------------------------------------
// deleteFromStripe: empty SK → skipped (no Stripe call, returns early)
// ------------------------------------------------------------------
public function testDeleteFromStripeSkipsOnEmptyKey(): void
{
$customer = $this->makeCustomer('Alice', 'alice@example.com', 'cus_123');
$this->customerRepository->method('findBy')->willReturn([$customer]);
// Empty string key — guard must skip before reaching Stripe API
$tester = $this->execute('');
$this->assertSame(0, $tester->getStatusCode());
}
// ------------------------------------------------------------------
// deleteFromStripe: test placeholder SK → skipped
// ------------------------------------------------------------------
public function testDeleteFromStripeSkipsOnTestKey(): void
{
$customer = $this->makeCustomer('Alice', 'alice@example.com', 'cus_123');
$this->customerRepository->method('findBy')->willReturn([$customer]);
$tester = $this->execute('sk_test_***');
$this->assertSame(0, $tester->getStatusCode());
}
// ------------------------------------------------------------------
// deleteFromStripe: null stripeCustomerId → skipped
// ------------------------------------------------------------------
public function testDeleteFromStripeSkipsOnNullStripeCustomerId(): void
{
// stripeCustomerId is null → guard returns early even with a real-looking key
$customer = $this->makeCustomer('Alice', 'alice@example.com', null);
$this->customerRepository->method('findBy')->willReturn([$customer]);
// Use a key that passes the first guard but hits the second guard (null id)
$tester = $this->execute('sk_live_real_but_null_id');
$this->assertSame(0, $tester->getStatusCode());
}
// ------------------------------------------------------------------
// logger->info is called for each deleted customer
// ------------------------------------------------------------------
public function testLoggerInfoCalledForDeletedCustomer(): void
{
$customer = $this->makeCustomer('Alice', 'alice@example.com');
$this->customerRepository->method('findBy')->willReturn([$customer]);
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->once())
->method('info')
->with($this->stringContains('alice@example.com'));
$this->logger = $logger;
$tester = $this->execute();
$this->assertSame(0, $tester->getStatusCode());
}
}