em = $this->createStub(EntityManagerInterface::class); $this->repository = $this->createStub(AttestationRepository::class); $this->filesystem = $this->createStub(Filesystem::class); $this->api = $this->createStub(Api::class); $this->projectDir = sys_get_temp_dir().'/clean-test-'.bin2hex(random_bytes(4)); mkdir($this->projectDir, 0775, true); } 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 execute(): CommandTester { // Ensure dirs exist so DirectoryIterator doesn't fail in cleanOrphanFiles if (!is_dir($this->projectDir.'/var/rgpd/signed')) { mkdir($this->projectDir.'/var/rgpd/signed', 0775, true); } $command = new CleanAttestationsCommand( $this->em, $this->repository, $this->filesystem, $this->projectDir, 'https://fake.docuseal.test', 'fake-key', ); // Replace Api with stub $ref = new \ReflectionProperty(CleanAttestationsCommand::class, 'docuSealApi'); $ref->setValue($command, $this->api); $tester = new CommandTester($command); $tester->execute([]); return $tester; } private function mockQueryReturning(array $results): void { $query = $this->createStub(Query::class); $query->method('getResult')->willReturn($results); $qb = $this->createStub(QueryBuilder::class); $qb->method('where')->willReturnSelf(); $qb->method('setParameter')->willReturnSelf(); $qb->method('getQuery')->willReturn($query); $this->repository->method('createQueryBuilder')->willReturn($qb); } public function testNoAttestationsToClean(): void { $this->mockQueryReturning([]); $tester = $this->execute(); $this->assertSame(0, $tester->getStatusCode()); $this->assertStringContainsString('Aucune attestation', $tester->getDisplay()); } public function testDeleteAttestationsWithFiles(): void { $attestation = new Attestation('access', '127.0.0.1', 'test@test.com', 'secret'); $attestation->setPdfFileUnsigned('/tmp/unsigned.pdf'); $attestation->setPdfFileSigned('/tmp/signed.pdf'); $attestation->setPdfFileCertificate('/tmp/cert.pdf'); $this->mockQueryReturning([$attestation]); $this->filesystem->method('exists')->willReturn(false); $tester = $this->execute(); $this->assertSame(0, $tester->getStatusCode()); $this->assertStringContainsString('1 attestation(s) supprimee(s)', $tester->getDisplay()); } public function testDeleteAttestationWithSubmitterId(): void { $attestation = new Attestation('deletion', '127.0.0.1', 'test@test.com', 'secret'); $attestation->setSubmitterId(42); $this->mockQueryReturning([$attestation]); $this->api->method('getSubmitter')->willReturn(['submission_id' => 99]); $this->filesystem->method('exists')->willReturn(false); $tester = $this->execute(); $this->assertSame(0, $tester->getStatusCode()); $this->assertStringContainsString('1 attestation(s) supprimee(s)', $tester->getDisplay()); } public function testDeleteAttestationSubmitterNoSubmissionId(): void { $attestation = new Attestation('no_data', '127.0.0.1', 'test@test.com', 'secret'); $attestation->setSubmitterId(42); $this->mockQueryReturning([$attestation]); $this->api->method('getSubmitter')->willReturn([]); $this->filesystem->method('exists')->willReturn(false); $tester = $this->execute(); $this->assertSame(0, $tester->getStatusCode()); } public function testDeleteFromDocuSealThrows(): void { $attestation = new Attestation('access', '127.0.0.1', 'test@test.com', 'secret'); $attestation->setSubmitterId(42); $this->mockQueryReturning([$attestation]); $this->api->method('getSubmitter')->willThrowException(new \RuntimeException('API error')); $this->filesystem->method('exists')->willReturn(false); $tester = $this->execute(); $this->assertSame(0, $tester->getStatusCode()); $this->assertStringContainsString('DocuSeal: impossible de supprimer', $tester->getDisplay()); } public function testCleanOrphanFilesInExistingDir(): void { $repository = $this->createStub(AttestationRepository::class); // Create orphan file with old mtime $rgpdDir = $this->projectDir.'/var/rgpd'; mkdir($rgpdDir, 0775, true); $orphanFile = $rgpdDir.'/orphan.pdf'; file_put_contents($orphanFile, 'orphan'); touch($orphanFile, time() - 86400 * 30); // Return one attestation so we get past the "aucune" check $attestation = new Attestation('access', '127.0.0.1', 'test@test.com', 'secret'); $query = $this->createStub(Query::class); $query->method('getResult')->willReturn([$attestation]); $qb = $this->createStub(QueryBuilder::class); $qb->method('where')->willReturnSelf(); $qb->method('setParameter')->willReturnSelf(); $qb->method('getQuery')->willReturn($query); $repository->method('createQueryBuilder')->willReturn($qb); $command = new CleanAttestationsCommand( $this->em, $repository, new Filesystem(), $this->projectDir, 'https://fake.docuseal.test', 'fake-key', ); $ref = new \ReflectionProperty(CleanAttestationsCommand::class, 'docuSealApi'); $ref->setValue($command, $this->api); $tester = new CommandTester($command); $tester->execute([]); $this->assertSame(0, $tester->getStatusCode()); $this->assertStringContainsString('fichier(s) orphelin(s) supprime(s)', $tester->getDisplay()); } public function testDeleteFileWithNullPath(): void { $attestation = new Attestation('access', '127.0.0.1', 'test@test.com', 'secret'); // No PDF paths set (all null) $this->mockQueryReturning([$attestation]); $this->filesystem->method('exists')->willReturn(false); $tester = $this->execute(); $this->assertSame(0, $tester->getStatusCode()); } public function testDeleteFileExistsOnDisk(): void { $attestation = new Attestation('access', '127.0.0.1', 'test@test.com', 'secret'); $attestation->setPdfFileUnsigned('/tmp/exists.pdf'); $this->mockQueryReturning([$attestation]); $this->filesystem->method('exists')->willReturn(true); $tester = $this->execute(); $this->assertSame(0, $tester->getStatusCode()); } }