Skip to content

Commit

Permalink
✨ Added support for publishing failure messages to SNS
Browse files Browse the repository at this point in the history
  • Loading branch information
yoan-myparcel committed Jan 29, 2024
1 parent 231f9c0 commit ee1be69
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 10 deletions.
12 changes: 12 additions & 0 deletions src/Sns/EmptyPayloadException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace MyParcelCom\Payments\Providers\Sns;

use RuntimeException;

class EmptyPayloadException extends RuntimeException
{

}
12 changes: 12 additions & 0 deletions src/Sns/FailureCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace MyParcelCom\Payments\Providers\Sns;

enum FailureCode: string
{
case FAILED = 'failed';
case CANCELLED = 'cancelled';
case EXPIRED = 'expired';
}
10 changes: 9 additions & 1 deletion src/Sns/PublishJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@ public function __construct(
private readonly string $topicArn,
private readonly string $myparcelcomPaymentId,
private readonly ?DateTimeInterface $paidAt = null,
private readonly ?FailureCode $failureCode = null,
private readonly ?string $failureMessage = null,
) {
}

public function handle(Publisher $publisher): void
{
$publisher
->publish($this->topicArn, $this->myparcelcomPaymentId, $this->paidAt)
->publish(
$this->topicArn,
$this->myparcelcomPaymentId,
$this->paidAt,
$this->failureCode,
$this->failureMessage,
)
->wait();
}
}
21 changes: 16 additions & 5 deletions src/Sns/Publisher.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,30 @@ public function __construct(
) {
}

public function publish(string $topicArn, string $myparcelcomPaymentId, ?DateTimeInterface $paidAt = null): PromiseInterface
{
$payload = [
public function publish(
string $topicArn,
string $myparcelcomPaymentId,
?DateTimeInterface $paidAt = null,
?FailureCode $failureCode = null,
?string $failureMessage = null,
): PromiseInterface {
if ($paidAt === null && $failureCode === null) {
throw new EmptyPayloadException();
}

$payload = array_filter([
'myparcelcom_payment_id' => $myparcelcomPaymentId,
'paid_at' => $paidAt?->format(DateTimeInterface::ATOM),
];
'failure_code' => $failureCode?->value,
'failure_message' => $failureMessage,
], static fn ($value) => $value !== null);

if (Env::get('APP_ENV') === 'local') {
return $this->localClient->publish($payload);
}

return $this->snsClient->publishAsync([
'Message' => Utils::jsonEncode(array_filter($payload, static fn ($value) => $value !== null)),
'Message' => Utils::jsonEncode($payload),
'TopicArn' => $topicArn,
]);
}
Expand Down
4 changes: 4 additions & 0 deletions tests/Http/SetupResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Faker\Factory;
use Illuminate\Http\Request;
use JsonException;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use MyParcelCom\Payments\Providers\Http\SetupResponse;
Expand All @@ -28,6 +29,9 @@ public function test_it_returns_no_content_response_when_no_authorization_url_is
assertEmpty($response->getContent());
}

/**
* @throws JsonException
*/
public function test_it_returns_json_response_when_authorization_url_is_set(): void
{
$faker = Factory::create();
Expand Down
8 changes: 7 additions & 1 deletion tests/Sns/PublishJobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ public function test_it_handles_publish_job(): void
$paidAt
) {
$mock->expects('publish')
->with($topicArn, $myparcelcomPaymentId, $paidAt)
->with(
$topicArn,
$myparcelcomPaymentId,
$paidAt,
null,
null,
)
->andReturns($snsPromise);
});

Expand Down
73 changes: 70 additions & 3 deletions tests/Sns/PublisherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use Mockery\MockInterface;
use MyParcelCom\Payments\Providers\Sns\EmptyPayloadException;
use MyParcelCom\Payments\Providers\Sns\FailureCode;
use MyParcelCom\Payments\Providers\Sns\LocalClient;
use MyParcelCom\Payments\Providers\Sns\Publisher;
use PHPUnit\Framework\TestCase;
Expand All @@ -20,7 +22,7 @@ class PublisherTest extends TestCase
{
use MockeryPHPUnitIntegration;

public function test_it_publishes_a_message_to_sns(): void
public function test_it_publishes_a_success_message_to_sns(): void
{
$faker = Factory::create();

Expand Down Expand Up @@ -49,10 +51,44 @@ public function test_it_publishes_a_message_to_sns(): void
$publisher->publish($topicArn, $myparcelcomPaymentId, $paidAt);
}

public function test_it_publishes_message_to_sns_without_paid_at(): void
public function test_it_publishes_a_failure_message_to_sns(): void
{
$faker = Factory::create();

$topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}";
$myparcelcomPaymentId = $faker->uuid;
$failureCode = $faker->randomElement(FailureCode::cases());
$failureMessage = $faker->sentence;

$snsClient = Mockery::mock(SnsClient::class, function (MockInterface & SnsClient $mock) use (
$topicArn,
$myparcelcomPaymentId,
$failureCode,
$failureMessage
) {
$mock
->expects('publishAsync')
->with([
'Message' => json_encode([
'myparcelcom_payment_id' => $myparcelcomPaymentId,
'failure_code' => $failureCode,
'failure_message' => $failureMessage,
], JSON_THROW_ON_ERROR),
'TopicArn' => $topicArn,
])
->andReturns(Mockery::mock(Promise::class));
});

$publisher = new Publisher($snsClient, Mockery::mock(LocalClient::class));
$publisher->publish($topicArn, $myparcelcomPaymentId, null, $failureCode, $failureMessage);
}

public function test_it_does_not_publish_message_to_sns_without_paid_at_or_failures(): void
{
$this->expectException(EmptyPayloadException::class);

$faker = Factory::create();

$topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}";
$myparcelcomPaymentId = $faker->uuid;

Expand All @@ -62,6 +98,7 @@ public function test_it_publishes_message_to_sns_without_paid_at(): void
) {
$mock
->expects('publishAsync')
->never()
->with([
'Message' => json_encode([
'myparcelcom_payment_id' => $myparcelcomPaymentId,
Expand All @@ -75,7 +112,7 @@ public function test_it_publishes_message_to_sns_without_paid_at(): void
$publisher->publish($topicArn, $myparcelcomPaymentId);
}

public function test_it_publishes_message_to_local_client(): void
public function test_it_publishes_success_message_to_local_client(): void
{
putenv('APP_ENV=local');
$faker = Factory::create();
Expand All @@ -101,4 +138,34 @@ public function test_it_publishes_message_to_local_client(): void

putenv('APP_ENV=');
}

public function test_it_publishes_fail_message_to_local_client(): void
{
putenv('APP_ENV=local');
$faker = Factory::create();

$topicArn = "arn:aws:sns:eu-west-1:{$faker->randomNumber()}:{$faker->word}";
$myparcelcomPaymentId = $faker->uuid;
$failureCode = $faker->randomElement(FailureCode::cases());
$failureMessage = $faker->sentence;

$localClient = Mockery::mock(LocalClient::class, function (MockInterface & LocalClient $mock) use (
$myparcelcomPaymentId,
$failureCode,
$failureMessage
) {
$mock
->expects('publish')
->with([
'myparcelcom_payment_id' => $myparcelcomPaymentId,
'failure_code' => $failureCode->value,
'failure_message' => $failureMessage,
]);
});

$publisher = new Publisher(Mockery::mock(SnsClient::class), $localClient);
$publisher->publish($topicArn, $myparcelcomPaymentId, null, $failureCode, $failureMessage);

putenv('APP_ENV=');
}
}

0 comments on commit ee1be69

Please sign in to comment.