Skip to content

Commit cd8139f

Browse files
committed
Add tests
1 parent 1419d83 commit cd8139f

10 files changed

Lines changed: 424 additions & 8 deletions

File tree

config/services/validators.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,13 @@ services:
4141
autowire: true
4242
autoconfigure: true
4343

44+
PhpList\RestBundle\Messaging\Validator\Constraint\MaxForwardCountValidator:
45+
autowire: true
46+
autoconfigure: true
47+
tags: [ 'validator.constraint_validator' ]
48+
49+
PhpList\RestBundle\Messaging\Validator\Constraint\MaxPersonalNoteSizeValidator:
50+
autowire: true
51+
autoconfigure: true
52+
tags: [ 'validator.constraint_validator' ]
53+

src/Common/EventListener/ExceptionListener.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Exception;
88
use PhpList\Core\Domain\Identity\Exception\AdminAttributeCreationException;
9+
use PhpList\Core\Domain\Messaging\Exception\MessageNotReceivedException;
910
use PhpList\Core\Domain\Subscription\Exception\AttributeDefinitionCreationException;
1011
use PhpList\Core\Domain\Subscription\Exception\SubscriptionCreationException;
1112
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -17,6 +18,9 @@
1718

1819
class ExceptionListener
1920
{
21+
/**
22+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
23+
*/
2024
public function onKernelException(ExceptionEvent $event): void
2125
{
2226
$exception = $event->getThrowable();
@@ -58,6 +62,11 @@ public function onKernelException(ExceptionEvent $event): void
5862
'message' => $exception->getMessage(),
5963
], 403);
6064
$event->setResponse($response);
65+
} elseif ($exception instanceof MessageNotReceivedException) {
66+
$response = new JsonResponse([
67+
'message' => $exception->getMessage(),
68+
], 422);
69+
$event->setResponse($response);
6170
} elseif ($exception instanceof Exception) {
6271
$response = new JsonResponse([
6372
'message' => $exception->getMessage(),

src/Messaging/Controller/EmailForwardController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public function forwardMessage(
9696
if ($message === null) {
9797
throw $this->createNotFoundException('Campaign not found.');
9898
}
99-
99+
// todo: per-subscriber forwarding limits
100100
/** @var ForwardMessageRequest $forwardRequest */
101101
$forwardRequest = $this->validator->validate($request, ForwardMessageRequest::class);
102102

src/Messaging/Request/ForwardMessageRequest.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
property: 'uid',
2525
type: 'string',
2626
example: 'fwd-123e4567-e89b-12d3-a456-426614174000',
27-
nullable: true
27+
nullable: false
2828
),
2929
new OA\Property(
3030
property: 'note',
@@ -36,14 +36,14 @@
3636
property: 'from_name',
3737
type: 'string',
3838
example: 'Alice',
39-
nullable: true
39+
nullable: false
4040
),
4141
new OA\Property(
4242
property: 'from_email',
4343
type: 'string',
4444
format: 'email',
4545
example: 'alice@example.com',
46-
nullable: true
46+
nullable: false
4747
),
4848
],
4949
type: 'object'
@@ -66,18 +66,24 @@ class ForwardMessageRequest implements RequestInterface
6666
])]
6767
public array $recipients = [];
6868

69+
#[Assert\NotNull]
70+
#[Assert\NotBlank]
6971
#[Assert\Length(max: 255)]
70-
public ?string $uid = null;
72+
public string $uid;
7173

7274
#[MaxPersonalNoteSize]
7375
public ?string $note = null;
7476

77+
#[Assert\NotNull]
78+
#[Assert\NotBlank]
7579
#[Assert\Length(max: 255)]
76-
public ?string $fromName = null;
80+
public string $fromName;
7781

82+
#[Assert\NotNull]
83+
#[Assert\NotBlank]
7884
#[Assert\Email]
7985
#[Assert\Length(max: 255)]
80-
public ?string $fromEmail = null;
86+
public string $fromEmail;
8187

8288
public function getDto(): array
8389
{

src/Messaging/Validator/Constraint/MaxPersonalNoteSizeValidator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function validate($value, Constraint $constraint): void
2121
return;
2222
}
2323

24-
if ($value === null || $value === '' || $this->maxSize === null || $this->maxSize < 0) {
24+
if ($value === null || $value === '' || $this->maxSize === null || $this->maxSize <= 0) {
2525
return;
2626
}
2727

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Tests\Integration\Messaging\Controller;
6+
7+
use PhpList\RestBundle\Messaging\Controller\EmailForwardController;
8+
use PhpList\RestBundle\Tests\Integration\Common\AbstractTestController;
9+
use PhpList\RestBundle\Tests\Integration\Identity\Fixtures\AdministratorFixture;
10+
use PhpList\RestBundle\Tests\Integration\Identity\Fixtures\AdministratorTokenFixture;
11+
use PhpList\RestBundle\Tests\Integration\Messaging\Fixtures\MessageFixture;
12+
use Symfony\Component\HttpFoundation\Response;
13+
14+
class EmailForwardControllerTest extends AbstractTestController
15+
{
16+
public function testControllerIsAvailableViaContainer(): void
17+
{
18+
self::assertInstanceOf(
19+
EmailForwardController::class,
20+
self::getContainer()->get(EmailForwardController::class)
21+
);
22+
}
23+
24+
public function testForwardWithInvalidPayloadReturnsUnprocessableEntity(): void
25+
{
26+
$this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class, MessageFixture::class]);
27+
28+
// Missing required 'recipients' field should trigger 422 from RequestValidator
29+
$this->authenticatedJsonRequest('POST', '/api/v2/email-forward/1', content: json_encode([ ]));
30+
31+
$this->assertHttpUnprocessableEntity();
32+
}
33+
34+
public function testForwardWithValidDataButNotReceivedEmail(): void
35+
{
36+
$this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class, MessageFixture::class]);
37+
38+
$payload = json_encode([
39+
'recipients' => ['friend1@example.com'],
40+
'uid' => 'fwd-123',
41+
'note' => null,
42+
'from_name' => 'Alice',
43+
'from_email' => 'alice@example.com',
44+
]);
45+
46+
$this->authenticatedJsonRequest('POST', '/api/v2/email-forward/1', content: $payload);
47+
48+
$response = self::getClient()->getResponse();
49+
$this->assertHttpUnprocessableEntity();
50+
self::assertStringContainsString('application/json', (string)$response->headers);
51+
52+
$data = $this->getDecodedJsonResponseContent();
53+
self::assertIsArray($data);
54+
self::assertArrayHasKey('message', $data);
55+
self::assertStringContainsString('Cannot forward: user has not received this message', $data['message']);
56+
}
57+
58+
public function testForwardWithInvalidEmailInRecipientsReturnsUnprocessableEntity(): void
59+
{
60+
$this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class, MessageFixture::class]);
61+
62+
$payload = json_encode([
63+
'recipients' => ['not-an-email'],
64+
]);
65+
66+
$this->authenticatedJsonRequest('POST', '/api/v2/email-forward/1', content: $payload);
67+
68+
$this->assertHttpUnprocessableEntity();
69+
}
70+
71+
public function testForwardWithInvalidIdReturnsNotFound(): void
72+
{
73+
$this->loadFixtures([AdministratorFixture::class, AdministratorTokenFixture::class]);
74+
75+
$this->authenticatedJsonRequest('POST', '/api/v2/email-forward/9999', content: json_encode([
76+
'recipients' => ['friend@example.com'],
77+
]));
78+
79+
$this->assertHttpNotFound();
80+
}
81+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Tests\Unit\Messaging\Request;
6+
7+
use PhpList\RestBundle\Messaging\Request\ForwardMessageRequest;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class ForwardMessageRequestTest extends TestCase
11+
{
12+
public function testGetDtoReturnsCorrectArray(): void
13+
{
14+
$request = new ForwardMessageRequest();
15+
16+
$request->recipients = ['friend1@example.com', 'friend2@example.com'];
17+
$request->uid = 'fwd-123e4567-e89b-12d3-a456-426614174000';
18+
$request->note = 'Thought you might like this.';
19+
$request->fromName = 'Alice';
20+
$request->fromEmail = 'alice@example.com';
21+
22+
$dto = $request->getDto();
23+
24+
$this->assertIsArray($dto);
25+
$this->assertSame(['friend1@example.com', 'friend2@example.com'], $dto['recipients']);
26+
$this->assertSame('fwd-123e4567-e89b-12d3-a456-426614174000', $dto['uid']);
27+
$this->assertSame('Thought you might like this.', $dto['note']);
28+
$this->assertSame('Alice', $dto['fromName']);
29+
$this->assertSame('alice@example.com', $dto['fromEmail']);
30+
}
31+
32+
public function testGetDtoHandlesNullables(): void
33+
{
34+
$request = new ForwardMessageRequest();
35+
36+
$request->recipients = ['friend@example.com'];
37+
$request->uid = 'fwd-uid-1';
38+
$request->note = null;
39+
$request->fromName = 'Bob';
40+
$request->fromEmail = 'bob@example.com';
41+
42+
$dto = $request->getDto();
43+
44+
$this->assertIsArray($dto);
45+
$this->assertSame(['friend@example.com'], $dto['recipients']);
46+
$this->assertSame('fwd-uid-1', $dto['uid']);
47+
$this->assertNull($dto['note']);
48+
$this->assertSame('Bob', $dto['fromName']);
49+
$this->assertSame('bob@example.com', $dto['fromEmail']);
50+
}
51+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Tests\Unit\Messaging\Serializer;
6+
7+
use PhpList\Core\Domain\Messaging\Model\Dto\ForwardingRecipientResult;
8+
use PhpList\Core\Domain\Messaging\Model\Dto\ForwardingResult;
9+
use PhpList\RestBundle\Messaging\Serializer\ForwardingResultNormalizer;
10+
use PHPUnit\Framework\TestCase;
11+
12+
class ForwardingResultNormalizerTest extends TestCase
13+
{
14+
private ForwardingResultNormalizer $normalizer;
15+
16+
protected function setUp(): void
17+
{
18+
$this->normalizer = new ForwardingResultNormalizer();
19+
}
20+
21+
public function testSupportsNormalizationReturnsTrueForForwardingResult(): void
22+
{
23+
$result = new ForwardingResult(
24+
totalRequested: 0,
25+
totalSent: 0,
26+
totalFailed: 0,
27+
totalAlreadySent: 0,
28+
recipients: [],
29+
);
30+
31+
$this->assertTrue($this->normalizer->supportsNormalization($result));
32+
}
33+
34+
public function testSupportsNormalizationReturnsFalseForOtherObjects(): void
35+
{
36+
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
37+
}
38+
39+
public function testNormalizeMapsAllTopLevelCounts(): void
40+
{
41+
$result = new ForwardingResult(
42+
totalRequested: 5,
43+
totalSent: 3,
44+
totalFailed: 1,
45+
totalAlreadySent: 1,
46+
recipients: [],
47+
);
48+
49+
$data = $this->normalizer->normalize($result);
50+
51+
$this->assertIsArray($data);
52+
$this->assertSame(5, $data['total_requested']);
53+
$this->assertSame(3, $data['total_sent']);
54+
$this->assertSame(1, $data['total_failed']);
55+
$this->assertSame(1, $data['total_already_sent']);
56+
$this->assertSame([], $data['recipients']);
57+
}
58+
59+
public function testNormalizeMapsRecipientsWithNullableReason(): void
60+
{
61+
$r1 = new ForwardingRecipientResult('a@example.com', 'sent', null);
62+
$r2 = new ForwardingRecipientResult('b@example.com', 'failed', 'precache_failed');
63+
64+
$result = new ForwardingResult(
65+
totalRequested: 2,
66+
totalSent: 1,
67+
totalFailed: 1,
68+
totalAlreadySent: 0,
69+
recipients: [$r1, $r2],
70+
);
71+
72+
$data = $this->normalizer->normalize($result);
73+
74+
$this->assertCount(2, $data['recipients']);
75+
$this->assertSame([
76+
'email' => 'a@example.com',
77+
'status' => 'sent',
78+
'reason' => null,
79+
], $data['recipients'][0]);
80+
$this->assertSame([
81+
'email' => 'b@example.com',
82+
'status' => 'failed',
83+
'reason' => 'precache_failed',
84+
], $data['recipients'][1]);
85+
}
86+
87+
public function testNormalizeReturnsEmptyArrayForNonForwardingResult(): void
88+
{
89+
$data = $this->normalizer->normalize(new \stdClass());
90+
$this->assertSame([], $data);
91+
}
92+
}

0 commit comments

Comments
 (0)