Skip to content

Commit e58f787

Browse files
committed
BounceRegexManager
1 parent f80edc8 commit e58f787

File tree

5 files changed

+258
-0
lines changed

5 files changed

+258
-0
lines changed

config/services/managers.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ services:
7575
PhpList\Core\Domain\Subscription\Service\Manager\SubscribePageManager:
7676
autowire: true
7777
autoconfigure: true
78+
79+
PhpList\Core\Domain\Messaging\Service\BounceRegexManager:
80+
autowire: true
81+
autoconfigure: true

config/services/repositories.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,8 @@ services:
130130
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
131131
arguments:
132132
- PhpList\Core\Domain\Subscription\Model\SubscribePageData
133+
134+
PhpList\Core\Domain\Messaging\Repository\BounceRegexRepository:
135+
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
136+
arguments:
137+
- PhpList\Core\Domain\Messaging\Model\BounceRegex

src/Domain/Messaging/Repository/BounceRegexRepository.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@
77
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
88
use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait;
99
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
10+
use PhpList\Core\Domain\Messaging\Model\BounceRegex;
1011

1112
class BounceRegexRepository extends AbstractRepository implements PaginatableRepositoryInterface
1213
{
1314
use CursorPaginationTrait;
15+
16+
public function findOneByRegexHash(string $regexHash): ?BounceRegex
17+
{
18+
return $this->findOneBy(['regexHash' => $regexHash]);
19+
}
1420
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Messaging\Service;
6+
7+
use Doctrine\ORM\EntityManagerInterface;
8+
use PhpList\Core\Domain\Messaging\Model\Bounce;
9+
use PhpList\Core\Domain\Messaging\Model\BounceRegex;
10+
use PhpList\Core\Domain\Messaging\Model\BounceRegexBounce;
11+
use PhpList\Core\Domain\Messaging\Repository\BounceRegexRepository;
12+
13+
class BounceRegexManager
14+
{
15+
private BounceRegexRepository $bounceRegexRepository;
16+
private EntityManagerInterface $entityManager;
17+
18+
public function __construct(
19+
BounceRegexRepository $bounceRegexRepository,
20+
EntityManagerInterface $entityManager
21+
) {
22+
$this->bounceRegexRepository = $bounceRegexRepository;
23+
$this->entityManager = $entityManager;
24+
}
25+
26+
/**
27+
* Creates or updates (if exists) a BounceRegex from a raw regex pattern.
28+
*/
29+
public function createOrUpdateFromPattern(
30+
string $regex,
31+
?string $action = null,
32+
?int $listOrder = 0,
33+
?int $admin = null,
34+
?string $comment = null,
35+
?string $status = null
36+
): BounceRegex {
37+
$regexHash = md5($regex);
38+
39+
$existing = $this->bounceRegexRepository->findOneByRegexHash($regexHash);
40+
41+
if ($existing !== null) {
42+
$existing->setRegex($regex)
43+
->setAction($action ?? $existing->getAction())
44+
->setListOrder($listOrder ?? $existing->getListOrder())
45+
->setAdmin($admin ?? $existing->getAdmin())
46+
->setComment($comment ?? $existing->getComment())
47+
->setStatus($status ?? $existing->getStatus());
48+
49+
$this->bounceRegexRepository->save($existing);
50+
51+
return $existing;
52+
}
53+
54+
$bounceRegex = new BounceRegex(
55+
regex: $regex,
56+
regexHash: $regexHash,
57+
action: $action,
58+
listOrder: $listOrder,
59+
admin: $admin,
60+
comment: $comment,
61+
status: $status,
62+
count: 0
63+
);
64+
65+
$this->bounceRegexRepository->save($bounceRegex);
66+
67+
return $bounceRegex;
68+
}
69+
70+
/** @return BounceRegex[] */
71+
public function getAll(): array
72+
{
73+
return $this->bounceRegexRepository->findAll();
74+
}
75+
76+
public function getByHash(string $regexHash): ?BounceRegex
77+
{
78+
return $this->bounceRegexRepository->findOneByRegexHash($regexHash);
79+
}
80+
81+
public function delete(BounceRegex $bounceRegex): void
82+
{
83+
$this->bounceRegexRepository->remove($bounceRegex);
84+
}
85+
86+
/**
87+
* Associates a bounce with the regex it matched and increments usage count.
88+
*/
89+
public function associateBounce(BounceRegex $regex, Bounce $bounce): BounceRegexBounce
90+
{
91+
$relation = new BounceRegexBounce($regex->getId() ?? 0, $bounce->getId() ?? 0);
92+
$this->entityManager->persist($relation);
93+
94+
$regex->setCount(($regex->getCount() ?? 0) + 1);
95+
$this->entityManager->flush();
96+
97+
return $relation;
98+
}
99+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Tests\Unit\Domain\Messaging\Service;
6+
7+
use Doctrine\ORM\EntityManagerInterface;
8+
use PhpList\Core\Domain\Messaging\Model\Bounce;
9+
use PhpList\Core\Domain\Messaging\Model\BounceRegex;
10+
use PhpList\Core\Domain\Messaging\Model\BounceRegexBounce;
11+
use PhpList\Core\Domain\Messaging\Repository\BounceRegexRepository;
12+
use PhpList\Core\Domain\Messaging\Service\BounceRegexManager;
13+
use PHPUnit\Framework\MockObject\MockObject;
14+
use PHPUnit\Framework\TestCase;
15+
use ReflectionProperty;
16+
17+
class BounceRegexManagerTest extends TestCase
18+
{
19+
private BounceRegexRepository&MockObject $regexRepository;
20+
private EntityManagerInterface&MockObject $entityManager;
21+
private BounceRegexManager $manager;
22+
23+
protected function setUp(): void
24+
{
25+
$this->regexRepository = $this->createMock(BounceRegexRepository::class);
26+
$this->entityManager = $this->createMock(EntityManagerInterface::class);
27+
28+
$this->manager = new BounceRegexManager(
29+
bounceRegexRepository: $this->regexRepository,
30+
entityManager: $this->entityManager
31+
);
32+
}
33+
34+
public function testCreateNewRegex(): void
35+
{
36+
$pattern = 'user unknown';
37+
$expectedHash = md5($pattern);
38+
39+
$this->regexRepository->expects($this->once())
40+
->method('findOneByRegexHash')
41+
->with($expectedHash)
42+
->willReturn(null);
43+
44+
$this->regexRepository->expects($this->once())
45+
->method('save')
46+
->with($this->isInstanceOf(BounceRegex::class));
47+
48+
$regex = $this->manager->createOrUpdateFromPattern(
49+
regex: $pattern,
50+
action: 'delete',
51+
listOrder: 5,
52+
admin: 1,
53+
comment: 'test',
54+
status: 'active'
55+
);
56+
57+
$this->assertInstanceOf(BounceRegex::class, $regex);
58+
$this->assertSame($pattern, $regex->getRegex());
59+
$this->assertSame($expectedHash, $regex->getRegexHash());
60+
$this->assertSame('delete', $regex->getAction());
61+
$this->assertSame(5, $regex->getListOrder());
62+
$this->assertSame(1, $regex->getAdmin());
63+
$this->assertSame('test', $regex->getComment());
64+
$this->assertSame('active', $regex->getStatus());
65+
}
66+
67+
public function testUpdateExistingRegex(): void
68+
{
69+
$pattern = 'mailbox full';
70+
$hash = md5($pattern);
71+
72+
$existing = new BounceRegex(
73+
regex: $pattern,
74+
regexHash: $hash,
75+
action: 'keep',
76+
listOrder: 0,
77+
admin: null,
78+
comment: null,
79+
status: 'inactive',
80+
count: 3
81+
);
82+
83+
$this->regexRepository->expects($this->once())
84+
->method('findOneByRegexHash')
85+
->with($hash)
86+
->willReturn($existing);
87+
88+
$this->regexRepository->expects($this->once())
89+
->method('save')
90+
->with($existing);
91+
92+
$updated = $this->manager->createOrUpdateFromPattern(
93+
regex: $pattern,
94+
action: 'delete',
95+
listOrder: 10,
96+
admin: 2,
97+
comment: 'upd',
98+
status: 'active'
99+
);
100+
101+
$this->assertSame('delete', $updated->getAction());
102+
$this->assertSame(10, $updated->getListOrder());
103+
$this->assertSame(2, $updated->getAdmin());
104+
$this->assertSame('upd', $updated->getComment());
105+
$this->assertSame('active', $updated->getStatus());
106+
$this->assertSame($hash, $updated->getRegexHash());
107+
}
108+
109+
public function testDeleteRegex(): void
110+
{
111+
$model = $this->createMock(BounceRegex::class);
112+
113+
$this->regexRepository->expects($this->once())
114+
->method('remove')
115+
->with($model);
116+
117+
$this->manager->delete($model);
118+
}
119+
120+
public function testAssociateBounceIncrementsCountAndPersistsRelation(): void
121+
{
122+
$regex = new BounceRegex(regex: 'x', regexHash: md5('x'));
123+
124+
$refRegex = new ReflectionProperty(BounceRegex::class, 'id');
125+
$refRegex->setValue($regex, 7);
126+
127+
$bounce = $this->createMock(Bounce::class);
128+
$bounce->method('getId')->willReturn(11);
129+
130+
$this->entityManager->expects($this->once())
131+
->method('persist')
132+
->with($this->callback(function ($entity) use ($regex) {
133+
return $entity instanceof BounceRegexBounce
134+
&& $entity->getRegex() === $regex->getId();
135+
}));
136+
137+
$this->entityManager->expects($this->once())
138+
->method('flush');
139+
140+
$this->assertSame(0, $regex->getCount());
141+
$this->manager->associateBounce($regex, $bounce);
142+
$this->assertSame(1, $regex->getCount());
143+
}
144+
}

0 commit comments

Comments
 (0)