Skip to content

Commit fe96d4f

Browse files
author
Stepan Zolotarev
committed
Add tests for simple retry strategy
1 parent 18b244f commit fe96d4f

File tree

5 files changed

+195
-24
lines changed

5 files changed

+195
-24
lines changed

src/Infrastructure/Logger/Handlers/Graylog/Formatter.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
use Monolog\Formatter\NormalizerFormatter;
1616
use Monolog\Level;
17-
use Monolog\Logger;
1817
use Monolog\LogRecord;
1918

2019
/**

src/Infrastructure/Logger/Handlers/StdOut/StdOutHandler.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use Monolog\Formatter\LineFormatter;
1818
use Monolog\Handler\AbstractProcessingHandler;
1919
use Monolog\Level;
20-
use Monolog\Logger;
2120
use Monolog\LogRecord;
2221

2322
/**

src/Retry/SimpleRetryStrategy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function () use ($message, $context, $details): \Generator {
8484
);
8585

8686
$context->logger()->info(
87-
'Resending an `{messageClass}` message to the queue wit delay `{delay}`',
87+
'Resending an `{messageClass}` message to the queue with delay `{delay}`',
8888
[
8989
'messageClass' => \get_class($message),
9090
'delay' => $delay,
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
/**
3+
* @author Stepan Zolotarev
4+
*/
5+
6+
declare(strict_types=1);
7+
8+
namespace ServiceBus\Tests\Retry;
9+
10+
use PHPUnit\Framework\TestCase;
11+
use ServiceBus\Common\EntryPoint\Retry\FailureContext;
12+
use ServiceBus\Common\Metadata\ServiceBusMetadata;
13+
use ServiceBus\MessageSerializer\Symfony\SymfonyJsonObjectSerializer;
14+
use ServiceBus\Retry\SimpleRetryStrategy;
15+
use ServiceBus\Storage\Common\DatabaseAdapter;
16+
use ServiceBus\Storage\Common\StorageConfiguration;
17+
use ServiceBus\Storage\Sql\DoctrineDBAL\DoctrineDBALAdapter;
18+
use ServiceBus\Storage\Sql\DoctrineDBAL\DoctrineDBALResultSet;
19+
use ServiceBus\Tests\EntryPoint\EntryPointTestMessage;
20+
use ServiceBus\Tests\TestContext;
21+
22+
use function Amp\Promise\wait;
23+
use function ServiceBus\Common\uuid;
24+
use function ServiceBus\Storage\Sql\fetchOne;
25+
26+
final class SimpleRetryStrategyTest extends TestCase
27+
{
28+
private DatabaseAdapter $databaseAdapter;
29+
private SymfonyJsonObjectSerializer $messageSerializer;
30+
private EntryPointTestMessage $message;
31+
private TestContext $context;
32+
private FailureContext $failureContext;
33+
34+
protected function setUp(): void
35+
{
36+
parent::setUp();
37+
38+
$this->databaseAdapter = new DoctrineDBALAdapter(
39+
new StorageConfiguration((string)\getenv('TEST_POSTGRES_DSN'))
40+
);
41+
42+
wait(
43+
$this->databaseAdapter->execute(
44+
'CREATE TABLE IF NOT EXISTS failed_messages
45+
(
46+
id uuid constraint failed_messages_pk primary key,
47+
message_id uuid not null,
48+
trace_id uuid not null,
49+
message_hash varchar not null,
50+
message_class varchar not null,
51+
message_payload bytea not null,
52+
failure_context jsonb not null,
53+
recorded_at timestamp not null,
54+
recovered_at timestamp
55+
);'
56+
)
57+
);
58+
59+
$this->messageSerializer = new SymfonyJsonObjectSerializer();
60+
$this->message = new EntryPointTestMessage(uuid());
61+
$this->context = new TestContext($this->message);
62+
$this->failureContext = new FailureContext([$this->message->id => 'some error']);
63+
}
64+
65+
/**
66+
* @test
67+
*/
68+
public function retry(): void
69+
{
70+
$retryStrategy = new SimpleRetryStrategy(
71+
databaseAdapter: $this->databaseAdapter,
72+
messageSerializer: $this->messageSerializer,
73+
maxRetryCount: 10,
74+
retryDelay: 1
75+
);
76+
77+
wait($retryStrategy->retry($this->message, $this->context, $this->failureContext));
78+
79+
self::assertCount(1, $this->context->messages);
80+
foreach ($this->context->messages as $key => $contextMessage) {
81+
self::assertSame($this->message, $contextMessage);
82+
self::assertArrayHasKey($key, $this->context->withMetadata);
83+
self::assertSame([
84+
ServiceBusMetadata::SERVICE_BUS_MESSAGE_RETRY_COUNT => 1,
85+
ServiceBusMetadata::SERVICE_BUS_MESSAGE_FAILED_IN => $this->message->id,
86+
], $this->context->withMetadata[$key]->variables());
87+
}
88+
89+
/** @var DoctrineDBALResultSet $resultSet */
90+
$resultSet = wait(
91+
$this->databaseAdapter->execute(
92+
'SELECT * FROM failed_messages WHERE message_id = ? AND trace_id = ?',
93+
[
94+
$this->context->metadata()->messageId(),
95+
$this->context->metadata()->traceId(),
96+
]
97+
)
98+
);
99+
$data = wait(fetchOne($resultSet));
100+
101+
self::assertNull($data);
102+
}
103+
104+
/**
105+
* @test
106+
*/
107+
public function retryWithMaxRetriesReached(): void
108+
{
109+
$retryStrategy = new SimpleRetryStrategy(
110+
databaseAdapter: $this->databaseAdapter,
111+
messageSerializer: $this->messageSerializer,
112+
maxRetryCount: 0,
113+
retryDelay: 1
114+
);
115+
116+
wait($retryStrategy->retry($this->message, $this->context, $this->failureContext));
117+
118+
self::assertCount(0, $this->context->messages);
119+
120+
/** @var DoctrineDBALResultSet $resultSet */
121+
$resultSet = wait(
122+
$this->databaseAdapter->execute(
123+
'SELECT * FROM failed_messages WHERE message_id = ? AND trace_id = ?',
124+
[
125+
$this->context->metadata()->messageId(),
126+
$this->context->metadata()->traceId(),
127+
]
128+
)
129+
);
130+
$data = wait(fetchOne($resultSet));
131+
132+
self::assertIsArray($data);
133+
self::assertArrayHasKey('message_class', $data);
134+
self::assertSame($data['message_class'], EntryPointTestMessage::class);
135+
}
136+
137+
/**
138+
* @test
139+
*/
140+
public function backoff(): void
141+
{
142+
$retryStrategy = new SimpleRetryStrategy(
143+
databaseAdapter: $this->databaseAdapter,
144+
messageSerializer: $this->messageSerializer,
145+
maxRetryCount: 0,
146+
retryDelay: 1
147+
);
148+
149+
wait($retryStrategy->backoff($this->message, $this->context, $this->failureContext));
150+
151+
/** @var DoctrineDBALResultSet $resultSet */
152+
$resultSet = wait(
153+
$this->databaseAdapter->execute(
154+
'SELECT * FROM failed_messages WHERE message_id = ? AND trace_id = ?',
155+
[
156+
$this->context->metadata()->messageId(),
157+
$this->context->metadata()->traceId(),
158+
]
159+
)
160+
);
161+
$data = wait(fetchOne($resultSet));
162+
163+
self::assertIsArray($data);
164+
self::assertArrayHasKey('message_class', $data);
165+
self::assertSame($data['message_class'], EntryPointTestMessage::class);
166+
}
167+
}

tests/TestContext.php

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,30 @@
3333
*/
3434
final class TestContext implements ServiceBusContext
3535
{
36-
/**
37-
* @var object
38-
*/
39-
private $incomeMessage;
36+
private object $incomeMessage;
4037

41-
/**
42-
* @var object[]
43-
*/
44-
public $messages = [];
38+
private ReceivedMessageMetadata $receivedMessageMetadata;
4539

4640
/**
47-
* @var TestHandler
41+
* @var array<string, object>
4842
*/
49-
public $testLogHandler;
43+
public array $messages = [];
5044

5145
/**
52-
* @var LoggerInterface
46+
* @var array<string, OutcomeMessageMetadata>
5347
*/
54-
private $logger;
48+
public array $withMetadata = [];
5549

56-
/**
57-
* @var ValidationViolations|null
58-
*/
59-
private $violations;
50+
public TestHandler $testLogHandler;
51+
52+
private LoggerInterface $logger;
53+
54+
private ?ValidationViolations $violations = null;
6055

6156
public function __construct(object $incomeMessage)
6257
{
6358
$this->incomeMessage = $incomeMessage;
59+
$this->receivedMessageMetadata = new ReceivedMessageMetadata(uuid(), uuid(), []);
6460
$this->testLogHandler = new TestHandler();
6561
$this->logger = new Logger(
6662
__CLASS__,
@@ -79,8 +75,13 @@ public function delivery(
7975
?OutcomeMessageMetadata $withMetadata = null
8076
): Promise {
8177
return call(
82-
function () use ($message) {
83-
$this->messages[] = $message;
78+
function () use ($message, $withMetadata) {
79+
$id = \spl_object_hash($message);
80+
81+
$this->messages[$id] = $message;
82+
if ($withMetadata !== null) {
83+
$this->withMetadata[$id] = $withMetadata;
84+
}
8485
}
8586
);
8687
}
@@ -91,9 +92,14 @@ public function deliveryBulk(
9192
?OutcomeMessageMetadata $withMetadata = null
9293
): Promise {
9394
return call(
94-
function () use ($messages) {
95+
function () use ($messages, $withMetadata) {
9596
foreach ($messages as $message) {
96-
$this->messages[] = $message;
97+
$id = \spl_object_hash($message);
98+
99+
$this->messages[$id] = $message;
100+
if ($withMetadata !== null) {
101+
$this->withMetadata[$id] = $withMetadata;
102+
}
97103
}
98104
}
99105
);
@@ -111,7 +117,7 @@ public function headers(): array
111117

112118
public function metadata(): IncomingMessageMetadata
113119
{
114-
return new ReceivedMessageMetadata(uuid(), uuid(), []);
120+
return $this->receivedMessageMetadata;
115121
}
116122

117123
/**

0 commit comments

Comments
 (0)