File: /home/xedaptot/ai.naniguide.com/tests/Unit/SendingServers/DriverCapabilityMatrixTest.php
<?php
use App\Model\SendingServer;
use App\SendingServers\Capabilities\AllowsCrossSendingDomain;
use App\SendingServers\Capabilities\AllowsLocalDomainVerify;
use App\SendingServers\Capabilities\AllowsLocalEmailVerify;
use App\SendingServers\Capabilities\ReceivesWebhooks;
use App\SendingServers\Capabilities\SendsCustomVerificationEmail;
use App\SendingServers\Capabilities\SignsDkimOnServer;
use App\SendingServers\Capabilities\SupportsCustomReturnPath;
use App\SendingServers\Capabilities\SupportsIdentitySync;
use App\SendingServers\Capabilities\SupportsRemoteDomainVerify;
use App\SendingServers\DriverRegistry;
use App\SendingServers\Drivers\SendingDriver;
use Tests\TestCase;
uses(TestCase::class);
/**
* Capability matrix — every built-in driver must implement exactly the set of
* `App\SendingServers\Capabilities\*` interfaces matching the legacy boolean
* flag table. Regression catch: if a driver forgets to implement a capability
* that legacy code exposed, call sites guarded by `instanceof <Capability>`
* silently skip vendor logic — bugs only surface in production.
*
* Each row asserts the FULL set; missing OR extra capability fails the test.
*/
/**
* Build a driver instance from a fresh in-memory SendingServer (no DB needed).
*/
function makeDriverForType(string $type): SendingDriver
{
$server = new SendingServer(['type' => $type, 'name' => 'fixture']);
$server->uid = 'fixture-uid';
return DriverRegistry::resolve($server);
}
/**
* Per-vendor expected capability list. Source of truth: legacy boolean
* methods on each STI subclass before the migration. See doc §F.4 + §2.3.
*/
dataset('driverCapabilities', [
'sendgrid-api' => [
'sendgrid-api',
[ReceivesWebhooks::class, SupportsIdentitySync::class, SupportsRemoteDomainVerify::class],
// NOT: AllowsLocalDomainVerify, AllowsLocalEmailVerify, AllowsCrossSendingDomain, SignsDkimOnServer
],
'sendgrid-smtp' => [
'sendgrid-smtp',
[SupportsIdentitySync::class, SupportsRemoteDomainVerify::class],
],
'mailgun-api' => [
'mailgun-api',
[ReceivesWebhooks::class, SupportsIdentitySync::class],
],
'mailgun-smtp' => [
'mailgun-smtp',
[SupportsIdentitySync::class],
],
'amazon-api' => [
'amazon-api',
[
ReceivesWebhooks::class,
SupportsIdentitySync::class,
SupportsRemoteDomainVerify::class,
SendsCustomVerificationEmail::class,
SignsDkimOnServer::class,
],
],
'amazon-smtp' => [
'amazon-smtp',
[
ReceivesWebhooks::class,
SupportsIdentitySync::class,
SupportsRemoteDomainVerify::class,
SendsCustomVerificationEmail::class,
SignsDkimOnServer::class,
],
],
'elasticemail-api' => [
'elasticemail-api',
[ReceivesWebhooks::class, SupportsIdentitySync::class, SupportsRemoteDomainVerify::class],
],
'elasticemail-smtp' => [
'elasticemail-smtp',
[SupportsIdentitySync::class, SupportsRemoteDomainVerify::class],
],
'sparkpost-api' => [
'sparkpost-api',
[ReceivesWebhooks::class, SupportsIdentitySync::class],
],
'sparkpost-smtp' => [
'sparkpost-smtp',
[SupportsIdentitySync::class],
],
'blastengine-api' => [
'blastengine-api',
[ReceivesWebhooks::class, AllowsLocalDomainVerify::class, AllowsCrossSendingDomain::class],
],
'blastengine-smtp' => [
'blastengine-smtp',
[AllowsLocalDomainVerify::class, AllowsCrossSendingDomain::class],
],
'smtp' => [
'smtp',
[
AllowsLocalDomainVerify::class,
AllowsLocalEmailVerify::class,
AllowsCrossSendingDomain::class,
SupportsCustomReturnPath::class,
],
],
'sendmail' => [
'sendmail',
[
AllowsLocalDomainVerify::class,
AllowsLocalEmailVerify::class,
AllowsCrossSendingDomain::class,
SupportsCustomReturnPath::class,
],
],
'gmail-oauth' => [
'gmail-oauth',
[AllowsLocalDomainVerify::class, AllowsLocalEmailVerify::class, AllowsCrossSendingDomain::class],
],
]);
test('driver implements expected capability set', function (string $type, array $expectedCaps) {
$driver = makeDriverForType($type);
// Every driver implements core SendingDriver.
expect($driver)->toBeInstanceOf(SendingDriver::class);
// Required capabilities present.
foreach ($expectedCaps as $cap) {
expect($driver)->toBeInstanceOf($cap);
}
// Capabilities NOT in the expected set must NOT be implemented (regression
// catch: accidentally adding a capability would change call-site behavior).
$allCaps = [
ReceivesWebhooks::class,
SupportsIdentitySync::class,
SupportsRemoteDomainVerify::class,
SupportsCustomReturnPath::class,
SignsDkimOnServer::class,
AllowsLocalDomainVerify::class,
AllowsLocalEmailVerify::class,
AllowsCrossSendingDomain::class,
SendsCustomVerificationEmail::class,
];
$unexpected = array_diff($allCaps, $expectedCaps);
foreach ($unexpected as $cap) {
expect($driver)->not->toBeInstanceOf($cap);
}
})->with('driverCapabilities');
test('every registered driver type resolves to a SendingDriver', function () {
$registered = array_keys(DriverRegistry::all());
// 15 built-in + N plugin drivers — guard against shrinkage, allow growth.
expect(count($registered))->toBeGreaterThanOrEqual(15);
foreach ($registered as $type) {
$server = new SendingServer(['type' => $type, 'name' => 'fixture']);
$server->uid = 'fixture-' . $type;
$driver = DriverRegistry::resolve($server);
expect($driver)->toBeInstanceOf(SendingDriver::class);
expect($driver->getServiceName())->toBeString()->not->toBeEmpty();
}
});
test('driver getServiceName returns vendor-specific label', function () {
expect(makeDriverForType('sendgrid-api')->getServiceName())->toBe('SendGrid');
expect(makeDriverForType('sendgrid-smtp')->getServiceName())->toBe('SendGrid');
expect(makeDriverForType('mailgun-api')->getServiceName())->toBe('Mailgun');
expect(makeDriverForType('mailgun-smtp')->getServiceName())->toBe('Mailgun');
expect(makeDriverForType('amazon-api')->getServiceName())->toBe('Amazon SES');
expect(makeDriverForType('amazon-smtp')->getServiceName())->toBe('Amazon SES');
expect(makeDriverForType('elasticemail-api')->getServiceName())->toBe('ElasticEmail');
expect(makeDriverForType('elasticemail-smtp')->getServiceName())->toBe('ElasticEmail');
expect(makeDriverForType('sparkpost-api')->getServiceName())->toBe('SparkPost');
expect(makeDriverForType('sparkpost-smtp')->getServiceName())->toBe('SparkPost');
expect(makeDriverForType('blastengine-api')->getServiceName())->toBe('Blastengine');
expect(makeDriverForType('blastengine-smtp')->getServiceName())->toBe('Blastengine');
expect(makeDriverForType('smtp')->getServiceName())->toBe('SMTP');
expect(makeDriverForType('sendmail')->getServiceName())->toBe('Sendmail');
expect(makeDriverForType('gmail-oauth')->getServiceName())->toBe('Gmail OAuth2');
});
test('webhookUrl per-instance routing builds /webhook/{type}/{uid}', function () {
$server = new SendingServer(['type' => 'sendgrid-api', 'name' => 'fixture']);
$server->uid = 'srv-abc-123';
$driver = DriverRegistry::resolve($server);
expect($driver)->toBeInstanceOf(ReceivesWebhooks::class);
/** @var ReceivesWebhooks $driver */
$url = $driver->webhookUrl();
expect($url)->toContain('/webhook/sendgrid-api/srv-abc-123');
});
test('Sendmail driver test() returns failure when path missing', function () {
$server = new SendingServer(['type' => 'sendmail', 'name' => 'fixture']);
$server->setConfig('sendmail_path', '/this/path/does/not/exist/sendmail');
$driver = DriverRegistry::resolve($server);
$result = $driver->test();
expect($result->ok)->toBeFalse();
expect($result->error)->toContain('does not exist');
});
test('Sendmail driver test() returns failure when path empty', function () {
$server = new SendingServer(['type' => 'sendmail', 'name' => 'fixture']);
// No sendmail_path set.
$driver = DriverRegistry::resolve($server);
$result = $driver->test();
expect($result->ok)->toBeFalse();
expect($result->error)->toContain('not configured');
});
test('Blastengine driver test() always returns success (legacy parity)', function () {
$driver = makeDriverForType('blastengine-api');
expect($driver->test()->ok)->toBeTrue();
});
test('SendingServer::driver() memoizes the driver instance', function () {
$server = new SendingServer(['type' => 'smtp', 'name' => 'fixture']);
$server->uid = 'srv-mem-1';
$a = $server->driver();
$b = $server->driver();
expect($a)->toBe($b); // same object reference
});
test('SendingServer::driver() throws on unknown type', function () {
$server = new SendingServer(['type' => 'nonexistent-vendor', 'name' => 'fixture']);
$server->uid = 'srv-bad';
expect(fn () => $server->driver())
->toThrow(\App\SendingServers\Exceptions\UnknownSendingServerType::class);
});