File: /home/xedaptot/ai.naniguide.com/tests/Unit/Sending/VariantResolverTest.php
<?php
/**
* VariantResolverTest — pure logic for picking the Email that drives a send.
*
* - Without a variant, returns the campaign's base email unmodified.
* - With a variant, returns the variant's email and merges the base's
* from_email / from_name / reply_to into any field the variant left empty.
* - Throws when the variant references a non-existent email row.
*
* No DB / no factories — uses Mockery + plain Email instances since the
* resolver only reads $campaign->email and $variant->email()->first().
*/
use App\Library\Sending\Variants\ResolvedEmail;
use App\Library\Sending\Variants\VariantResolver;
use App\Model\AbTestVariant;
use App\Model\Campaign;
use App\Model\Email;
use Tests\TestCase;
uses(TestCase::class);
it('returns the campaign base email when no variant is provided', function () {
$email = new Email(['from_email' => '[email protected]']);
$campaign = Mockery::mock(Campaign::class);
$campaign->shouldReceive('getAttribute')->with('email')->andReturn($email);
$resolved = (new VariantResolver())->resolve($campaign, null);
expect($resolved)->toBeInstanceOf(ResolvedEmail::class);
expect($resolved->email)->toBe($email);
expect($resolved->isVariant)->toBeFalse();
});
it('returns the variant email when variant has its own sender identity', function () {
$baseEmail = new Email([
'from_email' => '[email protected]',
'from_name' => 'Base Sender',
'reply_to' => '[email protected]',
]);
$variantEmail = new Email([
'from_email' => '[email protected]',
'from_name' => 'Variant Sender',
'reply_to' => '[email protected]',
]);
$campaign = Mockery::mock(Campaign::class);
$campaign->shouldReceive('getAttribute')->with('email')->andReturn($baseEmail);
$variant = Mockery::mock(AbTestVariant::class);
$emailRelation = Mockery::mock(\Illuminate\Database\Eloquent\Relations\BelongsTo::class);
$emailRelation->shouldReceive('first')->andReturn($variantEmail);
$variant->shouldReceive('email')->andReturn($emailRelation);
$resolved = (new VariantResolver())->resolve($campaign, $variant);
expect($resolved->email)->toBe($variantEmail);
expect($resolved->isVariant)->toBeTrue();
expect($resolved->email->from_email)->toBe('[email protected]');
expect($resolved->email->from_name)->toBe('Variant Sender');
expect($resolved->email->reply_to)->toBe('[email protected]');
});
it('inherits empty sender-identity fields from the base email', function () {
$baseEmail = new Email([
'from_email' => '[email protected]',
'from_name' => 'Base Sender',
'reply_to' => '[email protected]',
]);
$variantEmail = new Email([
'from_email' => '',
'from_name' => null,
'reply_to' => '',
]);
$campaign = Mockery::mock(Campaign::class);
$campaign->shouldReceive('getAttribute')->with('email')->andReturn($baseEmail);
$variant = Mockery::mock(AbTestVariant::class);
$emailRelation = Mockery::mock(\Illuminate\Database\Eloquent\Relations\BelongsTo::class);
$emailRelation->shouldReceive('first')->andReturn($variantEmail);
$variant->shouldReceive('email')->andReturn($emailRelation);
$resolved = (new VariantResolver())->resolve($campaign, $variant);
expect($resolved->email->from_email)->toBe('[email protected]');
expect($resolved->email->from_name)->toBe('Base Sender');
expect($resolved->email->reply_to)->toBe('[email protected]');
});
it('partially inherits — only fills the empty fields, keeps the variant ones', function () {
$baseEmail = new Email([
'from_email' => '[email protected]',
'from_name' => 'Base Sender',
'reply_to' => '[email protected]',
]);
$variantEmail = new Email([
'from_email' => '[email protected]',
'from_name' => '',
'reply_to' => '[email protected]',
]);
$campaign = Mockery::mock(Campaign::class);
$campaign->shouldReceive('getAttribute')->with('email')->andReturn($baseEmail);
$variant = Mockery::mock(AbTestVariant::class);
$emailRelation = Mockery::mock(\Illuminate\Database\Eloquent\Relations\BelongsTo::class);
$emailRelation->shouldReceive('first')->andReturn($variantEmail);
$variant->shouldReceive('email')->andReturn($emailRelation);
$resolved = (new VariantResolver())->resolve($campaign, $variant);
expect($resolved->email->from_email)->toBe('[email protected]'); // variant's
expect($resolved->email->from_name)->toBe('Base Sender'); // inherited
expect($resolved->email->reply_to)->toBe('[email protected]');// variant's
});
it('throws when the variant points at a missing email row', function () {
$campaign = Mockery::mock(Campaign::class);
$campaign->shouldReceive('getAttribute')->with('email')->andReturn(null);
$variant = Mockery::mock(AbTestVariant::class);
$variant->shouldReceive('getAttribute')->with('id')->andReturn(42);
$variant->shouldReceive('getAttribute')->with('email_id')->andReturn(999);
$emailRelation = Mockery::mock(\Illuminate\Database\Eloquent\Relations\BelongsTo::class);
$emailRelation->shouldReceive('first')->andReturn(null);
$variant->shouldReceive('email')->andReturn($emailRelation);
expect(fn () => (new VariantResolver())->resolve($campaign, $variant))
->toThrow(\RuntimeException::class, 'AbTestVariant 42 has no associated Email row (email_id=999)');
});
it('handles null base email gracefully (variant with no base to inherit from)', function () {
$variantEmail = new Email([
'from_email' => '[email protected]',
'from_name' => 'Variant Sender',
'reply_to' => '[email protected]',
]);
$campaign = Mockery::mock(Campaign::class);
$campaign->shouldReceive('getAttribute')->with('email')->andReturn(null);
$variant = Mockery::mock(AbTestVariant::class);
$emailRelation = Mockery::mock(\Illuminate\Database\Eloquent\Relations\BelongsTo::class);
$emailRelation->shouldReceive('first')->andReturn($variantEmail);
$variant->shouldReceive('email')->andReturn($emailRelation);
$resolved = (new VariantResolver())->resolve($campaign, $variant);
expect($resolved->email)->toBe($variantEmail);
expect($resolved->isVariant)->toBeTrue();
});