HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/tests/Unit/SendingServers/DriverSendTest.php
<?php

use App\Model\SendingServer;
use App\SendingServers\Drivers\Vendors\Generic\SmtpDriver;
use App\SendingServers\Drivers\Vendors\Mailgun\MailgunSmtpDriver;
use App\SendingServers\Drivers\Vendors\SendGrid\SendGridSmtpDriver;
use App\SendingServers\Drivers\Vendors\SparkPost\SparkPostSmtpDriver;
use Tests\TestCase;

uses(TestCase::class);

/**
 * Driver send() smoke tests — verify each driver's send() method:
 *   1. Reads config keys correctly (no undefined attribute errors).
 *   2. Builds Symfony Mailer transport / vendor SDK request without exception
 *      until the actual network call.
 *   3. Returns a SendResult DTO (typed getters: getRuntimeMessageId / getStatus).
 *
 * For each variant: assert NO undefined-method / undefined-property error
 * occurs before transport->start() / vendor SDK call.
 */

function buildTestMessage(string $msgId = 'test-msg-001'): \Symfony\Component\Mime\Email
{
    $email = new \Symfony\Component\Mime\Email();
    $email->subject('Test');
    $email->text('Body');
    $email->from('[email protected]');
    $email->to('[email protected]');
    $email->getHeaders()->addTextHeader('X-Acelle-Message-Id', $msgId);
    return $email;
}

test('SmtpDriver send() reads config without undefined-attribute errors', function () {
    $server = new SendingServer(['type' => 'smtp', 'name' => 'fixture']);
    $server->setConfigMany([
        'host' => 'localhost',     // unreachable port → expected SMTP error
        'smtp_port' => '1',
        'smtp_protocol' => 'tls',
        'smtp_username' => 'u',
        'smtp_password' => 'p',
    ]);
    $driver = new SmtpDriver($server);

    try {
        $driver->send(buildTestMessage());
    } catch (\Throwable $e) {
        // Accept network/transport failures. Reject undefined-method/property errors.
        expect($e)->not->toBeInstanceOf(\BadMethodCallException::class);
        expect($e->getMessage())->not->toContain('undefined method');
        expect($e->getMessage())->not->toContain('Undefined property');
    }
});

test('SendGridSmtpDriver send() builds transport from config + tags message with Smtpapi header', function () {
    $server = new SendingServer(['type' => 'sendgrid-smtp', 'name' => 'fixture']);
    $server->setConfigMany([
        'host' => 'localhost',
        'smtp_port' => '1',
        'smtp_protocol' => 'tls',
        'smtp_username' => 'u',
        'smtp_password' => 'p',
        'api_key' => 'sg-test',
    ]);
    $driver = new SendGridSmtpDriver($server);
    $message = buildTestMessage('sg-msg-001');

    try {
        $driver->send($message);
    } catch (\Throwable $e) {
        expect($e)->not->toBeInstanceOf(\BadMethodCallException::class);
    }

    // Verify the Smtpapi header was added to the message before transport call.
    expect($message->getHeaders()->has(\Smtpapi\Header::NAME))->toBeTrue();
});

test('MailgunSmtpDriver send() builds transport from config (no msgId tagging — legacy parity)', function () {
    $server = new SendingServer(['type' => 'mailgun-smtp', 'name' => 'fixture']);
    $server->setConfigMany([
        'host' => 'localhost',
        'smtp_port' => '1',
        'smtp_protocol' => 'tls',
        'smtp_username' => 'u',
        'smtp_password' => 'p',
        'api_key' => 'mg-test',
        'aws_region' => 'us',
    ]);
    $driver = new MailgunSmtpDriver($server);

    try {
        $driver->send(buildTestMessage());
    } catch (\Throwable $e) {
        expect($e)->not->toBeInstanceOf(\BadMethodCallException::class);
    }
});

test('SparkPostSmtpDriver send() tags message with X-MSYS-API metadata header', function () {
    $server = new SendingServer(['type' => 'sparkpost-smtp', 'name' => 'fixture']);
    $server->setConfigMany([
        'host' => 'localhost',
        'smtp_port' => '1',
        'smtp_protocol' => 'tls',
        'smtp_username' => 'u',
        'smtp_password' => 'p',
        'api_key' => 'sp-test',
    ]);
    $driver = new SparkPostSmtpDriver($server);
    $message = buildTestMessage('sp-msg-001');

    try {
        $driver->send($message);
    } catch (\Throwable $e) {
        expect($e)->not->toBeInstanceOf(\BadMethodCallException::class);
    }

    // X-MSYS-API header must contain the runtime_message_id metadata.
    $header = $message->getHeaders()->get('X-MSYS-API');
    expect($header)->not->toBeNull();
    $value = $header->getBodyAsString();
    $decoded = json_decode($value, true);
    expect($decoded['metadata']['runtime_message_id'])->toBe('sp-msg-001');
});

test('SendmailDriver send() returns failure result when path missing (defensive test)', function () {
    $server = new SendingServer(['type' => 'sendmail', 'name' => 'fixture']);
    $server->setConfig('sendmail_path', '/path/does/not/exist/sendmail');
    $driver = new \App\SendingServers\Drivers\Vendors\Generic\SendmailDriver($server);

    expect(fn () => $driver->send(buildTestMessage()))
        ->toThrow(\RuntimeException::class);
});

test('All drivers implement SendingDriver interface (no missing required method)', function () {
    foreach (\App\SendingServers\DriverRegistry::all() as $type => $class) {
        $reflection = new ReflectionClass($class);
        expect($reflection->isInstantiable())->toBeTrue();
        expect($reflection->implementsInterface(\App\SendingServers\Drivers\SendingDriver::class))->toBeTrue();

        // Required interface methods all defined (not abstract).
        foreach (['send', 'test', 'getServiceName', 'setupBeforeSend', 'getMessageId'] as $method) {
            $m = $reflection->getMethod($method);
            expect($m->isAbstract())->toBeFalse();
        }
    }
});

// ─── Regression: DKIM-signed message must remain typed as Email ──────────────
//
// Bug history: an older Acelle version had dkim_sign() return the DkimSigner
// output directly — that's typed Mime\Message (Email's parent). It then arrived
// at driver->send(Email) and raised TypeError deep in the worker pipeline.
//
// These tests pin the contract: dkim_sign + the SendingServer::send shim both
// preserve the Email type. PHP's own type-system enforces them at runtime; the
// tests assert the declared signatures match the contract.

test('dkim_sign() helper return type is Symfony\\Mime\\Email (parent regression guard)', function () {
    $reflection = new ReflectionFunction('App\\Helpers\\dkim_sign');
    expect((string) $reflection->getReturnType())->toBe('Symfony\\Component\\Mime\\Email');
});

test('SendingServer::send shim accepts Email and rejects bare Mime\\Message (parent)', function () {
    $reflection = new ReflectionMethod(\App\Model\SendingServer::class, 'send');
    $param = $reflection->getParameters()[0];
    expect((string) $param->getType())->toBe('Symfony\\Component\\Mime\\Email');
});

test('Every driver send() parameter is Email — not the broader Mime\\Message parent', function () {
    foreach (\App\SendingServers\DriverRegistry::all() as $type => $class) {
        $reflection = new ReflectionMethod($class, 'send');
        $param = $reflection->getParameters()[0];
        $declared = (string) $param->getType();
        expect($declared)
            ->toBe('Symfony\\Component\\Mime\\Email', "Driver {$type} send() parameter must be Email, got {$declared}");
    }
});

test('dkim_sign() preserves Email type when caller passes one in', function () {
    // Use a throwaway RSA keypair so we don't need a real Acelle domain row.
    $res = openssl_pkey_new(['private_key_bits' => 1024, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
    openssl_pkey_export($res, $privateKey);

    $domain = new \App\Library\Domain('example.com', 'sel1', '', $privateKey);

    $email = new \Symfony\Component\Mime\Email();
    $email->from('[email protected]')->to('[email protected]')
        ->subject('regression')->text('body')
        ->date(new \DateTimeImmutable())
        ->getHeaders()->addIdHeader('Message-ID', '[email protected]');

    $result = \App\Helpers\dkim_sign($email, $domain);

    expect($result)->toBeInstanceOf(\Symfony\Component\Mime\Email::class);
    // And the DKIM header DID get attached (sanity: helper is doing work).
    expect($result->getHeaders()->has('DKIM-Signature'))->toBeTrue();
});