File: /home/xedaptot/ai.naniguide.com/tests/Feature/EmailCustomHtmlTest.php
<?php
/**
* EmailCustomHtmlTest — verifies the "paste custom HTML" content flow.
*
* Covers the Option-1 design: uploaded HTML is stored in a Template row with
* source='uploaded'. The send pipeline (getBaseHtmlContent) is shared with
* builder templates; only the edit UX differs.
*/
use App\Model\Campaign;
use App\Model\Contact;
use App\Model\Customer;
use App\Model\Email;
use App\Model\MailList;
use App\Model\Template;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
uses(TestCase::class);
afterEach(function () {
$customerIds = DB::table('customers')->where('uid', 'like', 'custhtml_%')->pluck('id');
$campaignIds = DB::table('campaigns')->whereIn('customer_id', $customerIds)->pluck('id');
DB::table('campaigns_lists_segments')->whereIn('campaign_id', $campaignIds)->delete();
DB::table('campaigns')->whereIn('id', $campaignIds)->delete();
DB::table('emails')->whereIn('customer_id', $customerIds)->delete();
DB::table('mail_lists')->where('uid', 'like', 'listhtml_%')->delete();
DB::table('templates')->where('uid', 'like', 'tplhtml_%')->delete();
DB::table('templates')->where('name', 'like', 'Custom HTML Test %')->delete();
DB::table('customers')->whereIn('id', $customerIds)->delete();
DB::table('contacts')->where('uid', 'like', 'contacthtml_%')->delete();
});
function customHtmlFixture(): array
{
$contact = Contact::forceCreate([
'uid' => uniqid('contacthtml_'),
'first_name' => 'HTML',
'last_name' => 'User',
'email' => uniqid() . '@custom-html-test.invalid',
]);
$customer = Customer::forceCreate([
'uid' => uniqid('custhtml_'),
'contact_id' => $contact->id,
'status' => 'active',
'timezone' => 'UTC',
]);
$list = MailList::forceCreate([
'uid' => uniqid('listhtml_'),
'name' => 'Custom HTML List',
'customer_id' => $customer->id,
'from_email' => '[email protected]',
'from_name' => 'HTML Sender',
]);
$builderTemplate = Template::forceCreate([
'uid' => uniqid('tplhtml_'),
'name' => 'Builder Template',
'content' => '<html><body><p>Builder content</p></body></html>',
'json' => Template::DEFAULT_BUILDER_JSON,
'source' => Template::SOURCE_BUILDER,
]);
$thumbDir = storage_path('app/public/templates/thumb');
if (!is_dir($thumbDir)) {
mkdir($thumbDir, 0755, true);
}
file_put_contents($thumbDir . '/' . $builderTemplate->uid . '.jpg', '');
return compact('customer', 'list', 'builderTemplate');
}
function newCampaignForHtml(Customer $customer, MailList $list): Campaign
{
$campaign = $customer->local()->newDefaultCampaign();
$campaign->saveFromArray(['type' => Email::TYPE_REGULAR]);
$campaign->subject = 'HTML Test';
$campaign->from_email = '[email protected]';
$campaign->from_name = 'Sender';
$campaign->reply_to = '[email protected]';
$campaign->save();
$campaign->addListSegment($list, null);
$campaign->default_mail_list_id = $list->id;
$campaign->save();
return $campaign->fresh(['email']);
}
// ─── Tests ────────────────────────────────────────────────────────────────────
test('Template::SOURCE_* constants exist with expected values', function () {
expect(Template::SOURCE_BUILDER)->toBe('builder');
expect(Template::SOURCE_UPLOADED)->toBe('uploaded');
});
test('source is included in Template fillable', function () {
expect((new Template())->getFillable())->toContain('source');
});
test('Template::makeUploaded creates template with source=uploaded and non-empty json', function () {
$html = '<html><body><p>Hello Uploaded</p></body></html>';
$template = Template::makeUploaded('Custom HTML Test Make', $html);
expect($template->source)->toBe(Template::SOURCE_UPLOADED);
expect($template->content)->toBe($html);
expect($template->name)->toBe('Custom HTML Test Make');
expect($template->json)->not->toBeNull()->not->toBe('');
expect($template->isUploaded())->toBeTrue();
});
test('setCustomHtml creates uploaded template and assigns customer_id', function () {
['customer' => $customer, 'list' => $list] = customHtmlFixture();
$campaign = newCampaignForHtml($customer, $list);
$email = $campaign->email;
$html = '<html><body><p>Hello From Paste</p></body></html>';
$email->setCustomHtml($html, 'Custom HTML Test Paste');
$email->refresh();
expect($email->template)->not->toBeNull();
expect($email->template->source)->toBe(Template::SOURCE_UPLOADED);
expect($email->template->content)->toBe($html);
expect($email->isCustomHtml())->toBeTrue();
});
test('setCustomHtml on email with builder template cleans up old template', function () {
['customer' => $customer, 'list' => $list, 'builderTemplate' => $builder] = customHtmlFixture();
$campaign = newCampaignForHtml($customer, $list);
$email = $campaign->email;
$email->setTemplate($builder, 'Custom HTML Test Original');
$email->refresh();
$firstTemplateId = $email->template_id;
expect($firstTemplateId)->not->toBeNull();
$email->setCustomHtml('<html><body>Replaced</body></html>', 'Custom HTML Test Replacement');
$email->refresh();
expect($email->template_id)->not->toBe($firstTemplateId);
expect(DB::table('templates')->where('id', $firstTemplateId)->exists())->toBeFalse();
expect($email->template->source)->toBe(Template::SOURCE_UPLOADED);
});
test('getBaseHtmlContent runs full pipeline on uploaded email', function () {
['customer' => $customer, 'list' => $list] = customHtmlFixture();
$campaign = newCampaignForHtml($customer, $list);
$email = $campaign->email;
$email->preheader = 'Sneak peek copy';
$email->save();
$email->setCustomHtml('<html><body><p>Pipeline test</p></body></html>', 'Custom HTML Test Pipeline');
$email->refresh();
$output = $email->getBaseHtmlContent();
expect($output)->toContain('<!DOCTYPE');
expect($output)->toContain('Sneak peek copy');
expect($output)->toContain('Pipeline test');
});
test('Email::copy on uploaded email creates independent uploaded template', function () {
['customer' => $customer, 'list' => $list] = customHtmlFixture();
$campaign = newCampaignForHtml($customer, $list);
$email = $campaign->email;
$html = '<html><body><p>Original Upload</p></body></html>';
$email->setCustomHtml($html, 'Custom HTML Test Copy Source');
$email->refresh();
$originalTemplateId = $email->template_id;
$copy = $email->copy();
$copy->refresh();
expect($copy->template_id)->not->toBeNull();
expect($copy->template_id)->not->toBe($originalTemplateId);
expect($copy->template->source)->toBe(Template::SOURCE_UPLOADED);
expect($copy->template->content)->toBe($html);
expect($copy->isCustomHtml())->toBeTrue();
});
test('Email::deleteAndCleanup removes uploaded template', function () {
['customer' => $customer, 'list' => $list] = customHtmlFixture();
$campaign = newCampaignForHtml($customer, $list);
$email = $campaign->email;
$email->setCustomHtml('<html><body>Will be deleted</body></html>', 'Custom HTML Test Delete');
$email->refresh();
$templateId = $email->template_id;
expect(DB::table('templates')->where('id', $templateId)->exists())->toBeTrue();
// Detach email from campaign first (FK restricts direct email delete)
DB::table('campaigns_lists_segments')->where('campaign_id', $campaign->id)->delete();
DB::table('campaigns')->where('id', $campaign->id)->delete();
$email->deleteAndCleanup();
expect(DB::table('templates')->where('id', $templateId)->exists())->toBeFalse();
});
test('existing templates default to source=builder after migration backfill', function () {
$template = Template::forceCreate([
'uid' => uniqid('tplhtml_'),
'name' => 'Custom HTML Test Backfill',
'content' => '<html><body>x</body></html>',
'json' => Template::DEFAULT_BUILDER_JSON,
]);
$template->refresh();
expect($template->source)->toBe(Template::SOURCE_BUILDER);
expect($template->isUploaded())->toBeFalse();
});