File: /home/xedaptot/ai.naniguide.com/tests/Unit/SendingServers/Webhooks/BlastengineParseWebhookTest.php
<?php
use App\Model\BounceLog;
use App\Model\SendingServer;
use App\SendingServers\Drivers\Vendors\Blastengine\BlastengineApiDriver;
use App\SendingServers\Webhooks\BounceReceived;
use App\SendingServers\Webhooks\BounceType;
use App\SendingServers\Webhooks\IgnorableWebhookEvent;
use Illuminate\Http\Request;
use Tests\TestCase;
uses(TestCase::class);
/**
* Wave D.4 parity: Blastengine delegates to BounceLog::recordHardBounce()
* which has fallback lookup behaviors not in the standard listener path.
*
* Driver yields BounceReceived with `useLegacyRecordHardBounce: true` so
* the listener calls the static helper directly — preserves byte-for-byte
* parity (customer_id, fallback message_id lookup).
*/
function makeBlastengineDriver(): BlastengineApiDriver
{
$server = new SendingServer(['type' => 'blastengine-api', 'name' => 'fixture']);
$server->uid = 'fixture-uid';
return new BlastengineApiDriver($server);
}
function beReq(array $body): Request
{
return Request::create('/webhook/blastengine-api/fixture-uid', 'POST', [], [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode($body));
}
test('blastengine HARDERROR → BounceReceived with useLegacyRecordHardBounce flag', function () {
$driver = makeBlastengineDriver();
$events = iterator_to_array($driver->parseWebhook(beReq([
'events' => [
['event' => ['type' => 'HARDERROR', 'detail' => ['delivery_id' => 42]]],
],
])));
expect($events)->toHaveCount(1)
->and($events[0])->toBeInstanceOf(BounceReceived::class)
->and($events[0]->runtimeMessageId)->toBe('42')
->and($events[0]->type)->toBe(BounceType::HARD)
->and($events[0]->bounceTypeRaw)->toBe(BounceLog::HARD)
->and($events[0]->shouldBlacklist)->toBeTrue()
->and($events[0]->useLegacyRecordHardBounce)->toBeTrue(); // PARITY: delegates to recordHardBounce
});
test('blastengine DROP → BounceReceived (same as HARDERROR)', function () {
$driver = makeBlastengineDriver();
$events = iterator_to_array($driver->parseWebhook(beReq([
'events' => [
['event' => ['type' => 'DROP', 'detail' => ['delivery_id' => 7]]],
],
])));
expect($events[0])->toBeInstanceOf(BounceReceived::class)
->and($events[0]->useLegacyRecordHardBounce)->toBeTrue();
});
test('blastengine SOFTERROR → IgnorableWebhookEvent (parity skip)', function () {
$driver = makeBlastengineDriver();
$events = iterator_to_array($driver->parseWebhook(beReq([
'events' => [
['event' => ['type' => 'SOFTERROR', 'detail' => ['delivery_id' => 99]]],
],
])));
expect($events[0])->toBeInstanceOf(IgnorableWebhookEvent::class);
});
test('blastengine missing delivery_id → silent skip', function () {
$driver = makeBlastengineDriver();
$events = iterator_to_array($driver->parseWebhook(beReq([
'events' => [
['event' => ['type' => 'HARDERROR', 'detail' => []]],
],
])));
expect($events)->toBeEmpty();
});
test('blastengine rawForDb is full body (preserve recordHardBounce parity)', function () {
$driver = makeBlastengineDriver();
$body = [
'events' => [
['event' => ['type' => 'HARDERROR', 'detail' => ['delivery_id' => 42]]],
],
];
$events = iterator_to_array($driver->parseWebhook(beReq($body)));
// PARITY: legacy passed `$raw = file_get_contents('php://input')` to
// recordHardBounce, NOT json_encode of just one event.
expect($events[0]->rawForDb)->toBe(json_encode($body));
});