feat: send accepted teamer invoices to configurable email inbox

This commit is contained in:
Björn Fromme
2026-03-15 12:42:28 +01:00
parent 3cb5b065f4
commit 3b99757a08
13 changed files with 648 additions and 4 deletions
@@ -0,0 +1,190 @@
<?php
declare(strict_types=1);
namespace App\Tests\EventListener;
use App\Email\Mailer;
use App\Entity\Disposition;
use App\Entity\Teamer;
use App\Entity\Upload;
use App\Event\DocumentConfirmedEvent;
use App\EventListener\DatevEmailSubscriber;
use App\Model\EmailPathAttachmentDto;
use App\Service\Upload\UploadHandler;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class DatevEmailSubscriberTest extends TestCase
{
private Mailer&MockObject $mailer;
private UploadHandler&MockObject $uploadHandler;
private LoggerInterface&MockObject $logger;
private DatevEmailSubscriber $subscriber;
protected function setUp(): void
{
$this->mailer = $this->createMock(Mailer::class);
$this->uploadHandler = $this->createMock(UploadHandler::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->subscriber = new DatevEmailSubscriber(
$this->mailer,
$this->uploadHandler,
$this->logger,
'[email protected]',
'[email protected]',
);
}
public function testGetSubscribedEvents(): void
{
$events = DatevEmailSubscriber::getSubscribedEvents();
$this->assertArrayHasKey(DocumentConfirmedEvent::NAME, $events);
$this->assertSame('onDocumentConfirmed', $events[DocumentConfirmedEvent::NAME]);
}
public function testSkipsNonInvoiceDocuments(): void
{
$document = $this->createMock(Upload::class);
$document->method('getType')->willReturn(Upload::TYPE_CONTRACT);
$this->mailer
->expects($this->never())
->method('createAndSendEmail');
$event = new DocumentConfirmedEvent($document);
$this->subscriber->onDocumentConfirmed($event);
}
public function testSkipsDocumentWithoutDisposition(): void
{
$document = $this->createMock(Upload::class);
$document->method('getType')->willReturn(Upload::TYPE_INVOICE);
$document->method('getDisposition')->willReturn(null);
$document->method('getId')->willReturn(1);
$this->logger
->expects($this->once())
->method('warning')
->with('DATEV email skipped: document has no disposition', ['document' => 1]);
$this->mailer
->expects($this->never())
->method('createAndSendEmail');
$event = new DocumentConfirmedEvent($document);
$this->subscriber->onDocumentConfirmed($event);
}
public function testSkipsDispositionWithoutTeamer(): void
{
$disposition = $this->createMock(Disposition::class);
$disposition->method('getTeamer')->willReturn(null);
$document = $this->createMock(Upload::class);
$document->method('getType')->willReturn(Upload::TYPE_INVOICE);
$document->method('getDisposition')->willReturn($disposition);
$document->method('getId')->willReturn(2);
$this->logger
->expects($this->once())
->method('warning')
->with('DATEV email skipped: disposition has no teamer', ['document' => 2]);
$this->mailer
->expects($this->never())
->method('createAndSendEmail');
$event = new DocumentConfirmedEvent($document);
$this->subscriber->onDocumentConfirmed($event);
}
public function testSkipsMissingAttachmentFile(): void
{
$teamer = $this->createMock(Teamer::class);
$teamer->method('getFullName')->willReturn('Doe, John');
$disposition = $this->createMock(Disposition::class);
$disposition->method('getTeamer')->willReturn($teamer);
$document = $this->createMock(Upload::class);
$document->method('getType')->willReturn(Upload::TYPE_INVOICE);
$document->method('getDisposition')->willReturn($disposition);
$document->method('getId')->willReturn(3);
$nonExistentPath = '/tmp/non_existent_file_'.uniqid().'.pdf';
$this->uploadHandler
->method('getUploadFilepath')
->with($document)
->willReturn($nonExistentPath);
$this->logger
->expects($this->once())
->method('error')
->with('DATEV email skipped: attachment file not found', [
'document' => 3,
'path' => $nonExistentPath,
]);
$this->mailer
->expects($this->never())
->method('createAndSendEmail');
$event = new DocumentConfirmedEvent($document);
$this->subscriber->onDocumentConfirmed($event);
}
public function testSendsEmailWithAttachment(): void
{
$teamer = $this->createMock(Teamer::class);
$teamer->method('getFullName')->willReturn('Doe, John');
$teamer->method('__toString')->willReturn('Doe, John');
$disposition = $this->createMock(Disposition::class);
$disposition->method('getTeamer')->willReturn($teamer);
$document = $this->createMock(Upload::class);
$document->method('getDisposition')->willReturn($disposition);
$document->method('getType')->willReturn(Upload::TYPE_INVOICE);
$document->method('getId')->willReturn(42);
$document->method('getOriginalFilename')->willReturn('original.pdf');
$document->method('getCreatedAt')->willReturn(new \DateTimeImmutable('2026-01-15'));
$document->method('getFilename')->willReturn('abc123.pdf');
$tmpFile = tempnam(sys_get_temp_dir(), 'datev_test_');
try {
$this->uploadHandler
->expects($this->once())
->method('getUploadFilepath')
->with($document)
->willReturn($tmpFile);
$this->mailer
->expects($this->once())
->method('createAndSendEmail')
->willReturnCallback(function (array $context, array $options): void {
$this->assertSame('[email protected]', $options['to']);
$this->assertSame('[email protected]', $options['from']);
$this->assertSame('email/datev_invoice.html.twig', $options['template']);
$this->assertStringContainsString('Honorarnote', $options['subject']);
$this->assertArrayHasKey('attachments', $options);
$this->assertCount(1, $options['attachments']);
$attachment = $options['attachments'][0];
$this->assertInstanceOf(EmailPathAttachmentDto::class, $attachment);
$this->assertSame('hn-42-doe_john-20260115.pdf', $attachment->getName());
});
$event = new DocumentConfirmedEvent($document);
$this->subscriber->onDocumentConfirmed($event);
} finally {
unlink($tmpFile);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Tests\Model;
use App\Model\EmailAttachmentDto;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
class EmailAttachmentDtoTest extends TestCase
{
public function testAttachToAddsContentAttachment(): void
{
$dto = new EmailAttachmentDto('invoice.pdf', 'pdf-content', 'application/pdf');
$email = new TemplatedEmail();
$dto->attachTo($email);
$attachments = $email->getAttachments();
$this->assertCount(1, $attachments);
$body = $attachments[0]->getBody();
$this->assertSame('pdf-content', $body);
$this->assertSame('invoice.pdf', $attachments[0]->getFilename());
$this->assertSame('application/pdf', $attachments[0]->getMediaType().'/'.$attachments[0]->getMediaSubtype());
}
public function testGettersReturnConstructorValues(): void
{
$dto = new EmailAttachmentDto('test.csv', 'csv-data', 'text/csv');
$this->assertSame('test.csv', $dto->getName());
$this->assertSame('csv-data', $dto->getContent());
$this->assertSame('text/csv', $dto->getMimeType());
}
}
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace App\Tests\Model;
use App\Model\EmailPathAttachmentDto;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
class EmailPathAttachmentDtoTest extends TestCase
{
public function testAttachToAddsPathAttachment(): void
{
$tmpFile = tempnam(sys_get_temp_dir(), 'test_attachment_');
file_put_contents($tmpFile, 'dummy-pdf-content');
try {
$dto = new EmailPathAttachmentDto($tmpFile, 'invoice.pdf');
$email = new TemplatedEmail();
$dto->attachTo($email);
$attachments = $email->getAttachments();
$this->assertCount(1, $attachments);
$this->assertSame('invoice.pdf', $attachments[0]->getFilename());
$this->assertSame('application/pdf', $attachments[0]->getMediaType().'/'.$attachments[0]->getMediaSubtype());
} finally {
unlink($tmpFile);
}
}
public function testDefaultMimeTypeIsApplicationPdf(): void
{
$dto = new EmailPathAttachmentDto('/tmp/test.pdf', 'test.pdf');
$this->assertSame('application/pdf', $dto->getMimeType());
}
public function testCustomMimeType(): void
{
$dto = new EmailPathAttachmentDto('/tmp/test.csv', 'test.csv', 'text/csv');
$this->assertSame('text/csv', $dto->getMimeType());
}
public function testGettersReturnConstructorValues(): void
{
$dto = new EmailPathAttachmentDto('/var/uploads/file.pdf', 'document.pdf', 'application/pdf');
$this->assertSame('/var/uploads/file.pdf', $dto->getPath());
$this->assertSame('document.pdf', $dto->getName());
$this->assertSame('application/pdf', $dto->getMimeType());
}
}