Files
crm_ecosplay/tests/Service/DocuSealServiceTest.php
Serreau Jovann 8bda02888c test: couverture 83% methodes (1046 tests, 2135 assertions)
Entites completes a 100% :
- AdvertTest : 12 nouveaux (state, customer, totals, hmac, lines, payments)
- CustomerTest : 3 nouveaux (isPendingDelete, revendeurCode, updatedAt)
- DevisTest : 6 nouveaux (customer, submissionId, lines, state constants)
- FactureTest : 10 nouveaux (state, totals, isPaid, lines, hmac, splitIndex)
- OrderNumberTest : 1 nouveau (markAsUnused)
- WebsiteTest : 1 nouveau (revendeurCode)

Services completes/ameliores :
- DocuSealServiceTest : 30 nouveaux (sendDevis, resendDevis, download, compta)
- AdvertServiceTest : 6 nouveaux (isTvaEnabled, getTvaRate, computeTotals)
- DevisServiceTest : 6 nouveaux (idem)
- FactureServiceTest : 8 nouveaux (idem + createPaidFactureFromAdvert)
- MailerServiceTest : 7 nouveaux (unsubscribe headers, VCF, formatFileSize)
- MeilisearchServiceTest : 42 nouveaux (index/remove/search tous types)
- RgpdServiceTest : 6 nouveaux (sendVerificationCode, verifyCode)
- OrderNumberServiceTest : 2 nouveaux (preview/generate unused)
- TarificationServiceTest : 1 nouveau (stripe error logger)
- ComptaPdfTest : 4 nouveaux (totaux, colonnes numeriques, signature)
- FacturePdfTest : 6 nouveaux (QR code, RIB, CGV Twig, footer skip)

Controllers ameliores :
- ComptabiliteControllerTest : 13 nouveaux (JSON, PDF, sign, callback)
- StatsControllerTest : 2 nouveaux (rich data, 6-month evolution)
- SyncControllerTest : 13 nouveaux (sync 6 types + purge)
- ClientsControllerTest : 7 nouveaux (show, delete, resendWelcome)
- FactureControllerTest : 2 nouveaux (generatePdf 404, send success)
- LegalControllerTest : 6 nouveaux (rgpdVerify GET/POST)
- TarificationControllerTest : 3 nouveaux (purge paths)
- AdminControllersTest : 9 nouveaux (dashboard search, services)
- WebhookStripeControllerTest : 3 nouveaux (invalid signatures)
- KeycloakAuthenticatorTest : 4 nouveaux (groups, domain check)

Commands :
- PaymentReminderCommandTest : 1 nouveau (formalNotice step)
- TestMailCommandTest : 2 nouveaux (force-dsn success/failure)

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

695 lines
21 KiB
PHP

<?php
namespace App\Tests\Service;
use App\Entity\Attestation;
use App\Entity\Devis;
use App\Service\DocuSealService;
use Doctrine\ORM\EntityManagerInterface;
use Docuseal\Api;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class DocuSealServiceTest extends TestCase
{
private EntityManagerInterface $em;
private Api $api;
private DocuSealService $service;
private string $projectDir;
protected function setUp(): void
{
$this->em = $this->createStub(EntityManagerInterface::class);
$this->api = $this->createStub(Api::class);
$this->projectDir = sys_get_temp_dir().'/docuseal-test-'.bin2hex(random_bytes(4));
mkdir($this->projectDir.'/public', 0775, true);
$this->service = new DocuSealService($this->em, $this->createStub(LoggerInterface::class), 'https://fake.docuseal.test', 'fake-key', $this->projectDir);
// Replace the real Api with our stub
$ref = new \ReflectionProperty(DocuSealService::class, 'api');
$ref->setValue($this->service, $this->api);
}
protected function tearDown(): void
{
$this->removeDir($this->projectDir);
}
private function removeDir(string $dir): void
{
if (!is_dir($dir)) {
return;
}
foreach (scandir($dir) as $item) {
if ('.' === $item || '..' === $item) {
continue;
}
$path = $dir.'/'.$item;
is_dir($path) ? $this->removeDir($path) : unlink($path);
}
rmdir($dir);
}
private function createAttestation(?string $pdfPath = null, ?int $submitterId = null): Attestation
{
$attestation = new Attestation('access', '127.0.0.1', 'test@example.com', 'secret');
if (null !== $pdfPath) {
$attestation->setPdfFileUnsigned($pdfPath);
}
if (null !== $submitterId) {
$attestation->setSubmitterId($submitterId);
}
return $attestation;
}
// --- signAttestation ---
public function testSignAttestationSuccess(): void
{
$pdfPath = $this->projectDir.'/test.pdf';
file_put_contents($pdfPath, '%PDF-fake');
$attestation = $this->createAttestation($pdfPath);
$this->api->method('createSubmissionFromPdf')->willReturn([['id' => 42]]);
$result = $this->service->signAttestation($attestation);
$this->assertTrue($result);
$this->assertSame(42, $attestation->getSubmitterId());
}
public function testSignAttestationNullSubmitterId(): void
{
$pdfPath = $this->projectDir.'/test.pdf';
file_put_contents($pdfPath, '%PDF-fake');
$attestation = $this->createAttestation($pdfPath);
$this->api->method('createSubmissionFromPdf')->willReturn([[]]);
$this->assertFalse($this->service->signAttestation($attestation));
}
public function testSignAttestationNoPdfPath(): void
{
$attestation = $this->createAttestation();
$this->assertFalse($this->service->signAttestation($attestation));
}
public function testSignAttestationPdfFileDoesNotExist(): void
{
$attestation = $this->createAttestation('/nonexistent/file.pdf');
$this->assertFalse($this->service->signAttestation($attestation));
}
public function testSignAttestationApiThrows(): void
{
$pdfPath = $this->projectDir.'/test.pdf';
file_put_contents($pdfPath, '%PDF-fake');
$attestation = $this->createAttestation($pdfPath);
$this->api->method('createSubmissionFromPdf')->willThrowException(new \RuntimeException('API error'));
$this->assertFalse($this->service->signAttestation($attestation));
}
public function testSignAttestationWithLogoFile(): void
{
$pdfPath = $this->projectDir.'/test.pdf';
file_put_contents($pdfPath, '%PDF-fake');
file_put_contents($this->projectDir.'/public/logo_facture.png', 'fake-jpeg');
$attestation = $this->createAttestation($pdfPath);
$this->api->method('createSubmissionFromPdf')->willReturn([['id' => 99]]);
$this->assertTrue($this->service->signAttestation($attestation));
}
// --- downloadSignedDocuments ---
public function testDownloadSignedDocumentsNoSubmitterId(): void
{
$attestation = $this->createAttestation();
$this->assertFalse($this->service->downloadSignedDocuments($attestation));
}
public function testDownloadSignedDocumentsEmptyDocuments(): void
{
$attestation = $this->createAttestation(null, 42);
$this->api->method('getSubmitter')->willReturn(['documents' => []]);
$this->assertFalse($this->service->downloadSignedDocuments($attestation));
}
public function testDownloadSignedDocumentsApiThrows(): void
{
$attestation = $this->createAttestation(null, 42);
$this->api->method('getSubmitter')->willThrowException(new \RuntimeException('fail'));
$this->assertFalse($this->service->downloadSignedDocuments($attestation));
}
public function testDownloadSignedDocumentsNoPdfUrl(): void
{
$attestation = $this->createAttestation(null, 42);
$this->api->method('getSubmitter')->willReturn([
'documents' => [['name' => 'doc']],
]);
$this->assertFalse($this->service->downloadSignedDocuments($attestation));
$this->assertNull($attestation->getPdfFileSigned());
}
public function testDownloadSignedDocumentsWithPdfAndAudit(): void
{
$attestation = $this->createAttestation(null, 42);
// Create a temp file to serve as the "downloaded" PDF
$fakePdfPath = $this->projectDir.'/fake-signed.pdf';
file_put_contents($fakePdfPath, '%PDF-signed-content');
$fakeAuditPath = $this->projectDir.'/fake-audit.pdf';
file_put_contents($fakeAuditPath, '%PDF-audit');
$this->api->method('getSubmitter')->willReturn([
'documents' => [['url' => $fakePdfPath]],
'audit_log_url' => $fakeAuditPath,
]);
// file_get_contents works with local paths
$result = $this->service->downloadSignedDocuments($attestation);
$this->assertTrue($result);
$this->assertNotNull($attestation->getPdfFileSigned());
$this->assertNotNull($attestation->getPdfFileCertificate());
}
public function testDownloadSignedDocumentsNoAuditUrl(): void
{
$attestation = $this->createAttestation(null, 42);
$fakePdfPath = $this->projectDir.'/fake-signed.pdf';
file_put_contents($fakePdfPath, '%PDF-signed-content');
$this->api->method('getSubmitter')->willReturn([
'documents' => [['url' => $fakePdfPath]],
]);
$result = $this->service->downloadSignedDocuments($attestation);
$this->assertTrue($result);
$this->assertNotNull($attestation->getPdfFileSigned());
$this->assertNull($attestation->getPdfFileCertificate());
}
public function testDownloadSignedDocumentsInvalidPdfContent(): void
{
$attestation = $this->createAttestation(null, 42);
$fakePath = $this->projectDir.'/not-a-pdf.txt';
file_put_contents($fakePath, 'this is not a PDF');
$this->api->method('getSubmitter')->willReturn([
'documents' => [['url' => $fakePath]],
]);
$result = $this->service->downloadSignedDocuments($attestation);
$this->assertFalse($result);
$this->assertNull($attestation->getPdfFileSigned());
}
public function testDownloadSignedDocumentsCreatesDirectory(): void
{
$attestation = $this->createAttestation(null, 42);
$fakePdfPath = $this->projectDir.'/fake-signed.pdf';
file_put_contents($fakePdfPath, '%PDF-signed');
$this->api->method('getSubmitter')->willReturn([
'documents' => [['url' => $fakePdfPath]],
]);
// Ensure the signed dir doesn't exist yet
$signedDir = $this->projectDir.'/var/rgpd/signed';
$this->assertDirectoryDoesNotExist($signedDir);
$this->service->downloadSignedDocuments($attestation);
$this->assertDirectoryExists($signedDir);
}
// --- getApi ---
public function testGetApiReturnsApiInstance(): void
{
$api = $this->service->getApi();
$this->assertInstanceOf(Api::class, $api);
}
// --- sendDevisForSignature ---
private function createDevis(?string $pdfFilename = 'test.pdf', ?string $email = 'client@example.com'): Devis
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
if (null !== $email) {
$customer = $this->createStub(\App\Entity\Customer::class);
$customer->method('getEmail')->willReturn($email);
$customer->method('getFullName')->willReturn('Jean Dupont');
$devis->setCustomer($customer);
}
if (null !== $pdfFilename) {
$devis->setUnsignedPdf($pdfFilename);
}
return $devis;
}
public function testSendDevisForSignatureNoCustomer(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$result = $this->service->sendDevisForSignature($devis);
$this->assertNull($result);
}
public function testSendDevisForSignatureNoEmail(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$customer = $this->createStub(\App\Entity\Customer::class);
$customer->method('getEmail')->willReturn(null);
$devis->setCustomer($customer);
$result = $this->service->sendDevisForSignature($devis);
$this->assertNull($result);
}
public function testSendDevisForSignatureNoPdf(): void
{
$devis = $this->createDevis(null);
$result = $this->service->sendDevisForSignature($devis);
$this->assertNull($result);
}
public function testSendDevisForSignaturePdfNotFound(): void
{
$devis = $this->createDevis('nonexistent.pdf');
$result = $this->service->sendDevisForSignature($devis);
$this->assertNull($result);
}
public function testSendDevisForSignatureSuccess(): void
{
// Create the PDF file
$pdfPath = $this->projectDir.'/public/uploads/devis/test-devis.pdf';
mkdir(dirname($pdfPath), 0775, true);
file_put_contents($pdfPath, '%PDF-fake');
$devis = $this->createDevis('test-devis.pdf');
$this->api->method('createSubmissionFromPdf')->willReturn([
'submitters' => [['id' => 77]],
]);
$result = $this->service->sendDevisForSignature($devis);
$this->assertSame(77, $result);
}
public function testSendDevisForSignatureWithRedirectUrl(): void
{
$pdfPath = $this->projectDir.'/public/uploads/devis/test-devis2.pdf';
mkdir(dirname($pdfPath), 0775, true);
file_put_contents($pdfPath, '%PDF-fake');
$devis = $this->createDevis('test-devis2.pdf');
$this->api->method('createSubmissionFromPdf')->willReturn([
'submitters' => [['id' => 88]],
]);
$result = $this->service->sendDevisForSignature($devis, 'https://redirect.example.com');
$this->assertSame(88, $result);
}
public function testSendDevisForSignatureApiThrows(): void
{
$pdfPath = $this->projectDir.'/public/uploads/devis/test-devis3.pdf';
mkdir(dirname($pdfPath), 0775, true);
file_put_contents($pdfPath, '%PDF-fake');
$devis = $this->createDevis('test-devis3.pdf');
$this->api->method('createSubmissionFromPdf')->willThrowException(new \RuntimeException('API error'));
$result = $this->service->sendDevisForSignature($devis);
$this->assertNull($result);
}
// --- resendDevisSignature ---
public function testResendDevisSignatureNoOldSubmitter(): void
{
$pdfPath = $this->projectDir.'/public/uploads/devis/resend1.pdf';
mkdir(dirname($pdfPath), 0775, true);
file_put_contents($pdfPath, '%PDF-fake');
$devis = $this->createDevis('resend1.pdf');
// No submissionId set (zero)
$this->api->method('createSubmissionFromPdf')->willReturn([
'submitters' => [['id' => 55]],
]);
$result = $this->service->resendDevisSignature($devis);
$this->assertSame(55, $result);
}
public function testResendDevisSignatureWithOldSubmitter(): void
{
$pdfPath = $this->projectDir.'/public/uploads/devis/resend2.pdf';
mkdir(dirname($pdfPath), 0775, true);
file_put_contents($pdfPath, '%PDF-fake');
$devis = $this->createDevis('resend2.pdf');
$devis->setSubmissionId('123');
$this->api->method('getSubmitter')->willReturn(['submission_id' => 456]);
$this->api->method('createSubmissionFromPdf')->willReturn([
'submitters' => [['id' => 99]],
]);
$result = $this->service->resendDevisSignature($devis);
$this->assertSame(99, $result);
}
public function testResendDevisSignatureArchiveFails(): void
{
$pdfPath = $this->projectDir.'/public/uploads/devis/resend3.pdf';
mkdir(dirname($pdfPath), 0775, true);
file_put_contents($pdfPath, '%PDF-fake');
$devis = $this->createDevis('resend3.pdf');
$devis->setSubmissionId('200');
// getSubmitter throws => archive warning logged, then sendDevisForSignature called
$this->api->method('getSubmitter')->willThrowException(new \RuntimeException('not found'));
$this->api->method('createSubmissionFromPdf')->willReturn([
'submitters' => [['id' => 42]],
]);
$result = $this->service->resendDevisSignature($devis);
$this->assertSame(42, $result);
}
// --- getSubmitterSlug ---
public function testGetSubmitterSlugSuccess(): void
{
$this->api->method('getSubmitter')->willReturn(['slug' => 'abc-def-123']);
$slug = $this->service->getSubmitterSlug(42);
$this->assertSame('abc-def-123', $slug);
}
public function testGetSubmitterSlugNoSlugKey(): void
{
$this->api->method('getSubmitter')->willReturn(['id' => 42]);
$slug = $this->service->getSubmitterSlug(42);
$this->assertNull($slug);
}
public function testGetSubmitterSlugApiThrows(): void
{
$this->api->method('getSubmitter')->willThrowException(new \RuntimeException('fail'));
$slug = $this->service->getSubmitterSlug(42);
$this->assertNull($slug);
}
// --- getSubmitterData ---
public function testGetSubmitterDataSuccess(): void
{
$data = ['id' => 42, 'slug' => 'xyz', 'documents' => []];
$this->api->method('getSubmitter')->willReturn($data);
$result = $this->service->getSubmitterData(42);
$this->assertSame($data, $result);
}
public function testGetSubmitterDataApiThrows(): void
{
$this->api->method('getSubmitter')->willThrowException(new \RuntimeException('fail'));
$result = $this->service->getSubmitterData(42);
$this->assertNull($result);
}
// --- archiveSubmission ---
public function testArchiveSubmissionSuccess(): void
{
$result = $this->service->archiveSubmission(123);
$this->assertTrue($result);
}
public function testArchiveSubmissionApiThrows(): void
{
$this->api->method('archiveSubmission')->willThrowException(new \RuntimeException('fail'));
$result = $this->service->archiveSubmission(123);
$this->assertFalse($result);
}
// --- downloadSignedDevis ---
public function testDownloadSignedDevisNoSubmissionId(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$result = $this->service->downloadSignedDevis($devis);
$this->assertFalse($result);
}
public function testDownloadSignedDevisZeroSubmitterId(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$devis->setSubmissionId('0');
$result = $this->service->downloadSignedDevis($devis);
$this->assertFalse($result);
}
public function testDownloadSignedDevisEmptyDocuments(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$devis->setSubmissionId('42');
$this->api->method('getSubmitter')->willReturn(['documents' => []]);
$result = $this->service->downloadSignedDevis($devis);
$this->assertFalse($result);
}
public function testDownloadSignedDevisNoPdfUrl(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$devis->setSubmissionId('42');
$this->api->method('getSubmitter')->willReturn([
'documents' => [['name' => 'doc']],
]);
$result = $this->service->downloadSignedDevis($devis);
$this->assertFalse($result);
}
public function testDownloadSignedDevisInvalidPdfContent(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$devis->setSubmissionId('42');
$fakePath = $this->projectDir.'/not-a-pdf.txt';
file_put_contents($fakePath, 'not pdf content');
$this->api->method('getSubmitter')->willReturn([
'documents' => [['url' => $fakePath]],
]);
$result = $this->service->downloadSignedDevis($devis);
$this->assertFalse($result);
}
public function testDownloadSignedDevisSuccess(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$devis->setSubmissionId('42');
$fakePdf = $this->projectDir.'/signed-devis.pdf';
file_put_contents($fakePdf, '%PDF-signed');
$this->api->method('getSubmitter')->willReturn([
'documents' => [['url' => $fakePdf]],
]);
$result = $this->service->downloadSignedDevis($devis);
$this->assertTrue($result);
}
public function testDownloadSignedDevisWithAudit(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$devis->setSubmissionId('42');
$fakePdf = $this->projectDir.'/signed-devis2.pdf';
$fakeAudit = $this->projectDir.'/audit-devis.pdf';
file_put_contents($fakePdf, '%PDF-signed');
file_put_contents($fakeAudit, '%PDF-audit');
$this->api->method('getSubmitter')->willReturn([
'documents' => [['url' => $fakePdf]],
'audit_log_url' => $fakeAudit,
]);
$result = $this->service->downloadSignedDevis($devis);
$this->assertTrue($result);
}
public function testDownloadSignedDevisApiThrows(): void
{
$orderNumber = new \App\Entity\OrderNumber('04/2026-00001');
$devis = new Devis($orderNumber, 'secret');
$devis->setSubmissionId('42');
$this->api->method('getSubmitter')->willThrowException(new \RuntimeException('fail'));
$result = $this->service->downloadSignedDevis($devis);
$this->assertFalse($result);
}
// --- sendComptaForSignature ---
public function testSendComptaForSignatureSuccess(): void
{
$this->api->method('createSubmissionFromPdf')->willReturn([
'submitters' => [['id' => 111]],
]);
$result = $this->service->sendComptaForSignature(
'%PDF-content',
'Export Compta',
'user@example.com',
'Jean Dupont',
'fec',
'01/01/2026',
'31/03/2026'
);
$this->assertSame(111, $result);
}
public function testSendComptaForSignatureWithRedirectUrl(): void
{
$this->api->method('createSubmissionFromPdf')->willReturn([
'submitters' => [['id' => 222]],
]);
$result = $this->service->sendComptaForSignature(
'%PDF-content',
'Export Compta',
'user@example.com',
'Jean Dupont',
'grand_livre',
'01/01/2026',
'31/12/2026',
'https://redirect.example.com'
);
$this->assertSame(222, $result);
}
public function testSendComptaForSignatureFallbackId(): void
{
// Result has no 'submitters' key, fallback to result[0]['id']
$this->api->method('createSubmissionFromPdf')->willReturn([
['id' => 333],
]);
$result = $this->service->sendComptaForSignature(
'%PDF-content',
'Export Compta',
'user@example.com',
'Jean Dupont',
'balance',
'01/01/2026',
'31/12/2026'
);
$this->assertSame(333, $result);
}
public function testSendComptaForSignatureApiThrows(): void
{
$this->api->method('createSubmissionFromPdf')->willThrowException(new \RuntimeException('API error'));
$result = $this->service->sendComptaForSignature(
'%PDF-content',
'Export Compta',
'user@example.com',
'Jean Dupont',
'fec',
'01/01/2026',
'31/03/2026'
);
$this->assertNull($result);
}
}