File: /home/xedaptot/ai.naniguide.com/tests/Feature/TemplateResetCommandTest.php
<?php
namespace Tests\Feature;
use App\Model\FormTemplate;
use App\Model\SystemEmailTemplate;
use App\Model\SystemTemplate;
use App\Model\Template;
use App\Model\TemplateCategory;
use Illuminate\Support\Facades\Artisan;
use Tests\TestCase;
/**
* Integration test for `php artisan template:reset`.
*
* The command runs `db:seed --class=TemplateSeeder` which:
* 1. Wipes + recreates TemplateCategory rows (only Base + Extended remain
* under the unified taxonomy — see admin-templates-overhaul feature)
* 2. Wipes + recreates 19 SystemTemplate rows (sender_verification_email,
* sign_up_form, etc.)
* 3. Wipes + recreates 34 SystemEmailTemplate rows (13 Base + 21 Extended)
* 4. Wipes + recreates 4 FormTemplate rows (4 Base + 0 Extended; the canonical
* Popup_Form_Top/Bottom/Left/Right_Banner set)
* 5. Resets each MailList's default templates
*
* This test guards the unified-taxonomy invariants so a future seeder edit
* doesn't silently re-introduce legacy categories or break the Base/Extended
* split. Critical because the admin templates UI depends on this taxonomy.
*
* NOTE: This test is destructive — it actually wipes and re-seeds the DB
* tables for templates. It runs against the dev/CI database. The seeder is
* idempotent so the post-test state is the same canonical baseline.
*/
class TemplateResetCommandTest extends TestCase
{
public function test_template_reset_command_runs_successfully(): void
{
$exitCode = Artisan::call('template:reset');
$this->assertSame(0, $exitCode, 'template:reset must exit with code 0');
}
public function test_template_reset_creates_only_base_and_extended_categories(): void
{
Artisan::call('template:reset');
$names = TemplateCategory::orderBy('name')->pluck('name')->all();
$this->assertSame(['Base', 'Extended'], $names,
'TemplateCategory rows must be exactly [Base, Extended] under the unified taxonomy. ' .
'Legacy Basic / Featured / Popup categories were dropped — do NOT re-introduce them. ' .
'Found: ' . implode(', ', $names));
}
public function test_template_reset_seeds_34_system_email_templates_split_13_base_and_21_extended(): void
{
Artisan::call('template:reset');
$base = TemplateCategory::whereName('Base')->firstOrFail();
$extended = TemplateCategory::whereName('Extended')->firstOrFail();
$totalEmails = SystemEmailTemplate::count();
$baseEmails = SystemEmailTemplate::categoryUid($base->uid)->count();
$extEmails = SystemEmailTemplate::categoryUid($extended->uid)->count();
$this->assertSame(34, $totalEmails, "Expected exactly 34 system email templates, got {$totalEmails}");
$this->assertSame(13, $baseEmails,
"Expected 13 Base email templates (Blank → Make an announcement), got {$baseEmails}. " .
'Sentinel: the boundary between Base and Extended is "Sitewide sale".');
$this->assertSame(21, $extEmails,
"Expected 21 Extended email templates (Sitewide sale → Workspace AI Invite), got {$extEmails}");
$this->assertSame($totalEmails, $baseEmails + $extEmails,
'Every system email template must be classified as exactly one of Base or Extended (no orphans, no duplicates).');
}
public function test_template_reset_seeds_4_form_templates_all_classified_as_base(): void
{
Artisan::call('template:reset');
$base = TemplateCategory::whereName('Base')->firstOrFail();
$extended = TemplateCategory::whereName('Extended')->firstOrFail();
$totalForms = FormTemplate::count();
$baseForms = FormTemplate::categoryUid($base->uid)->count();
$extForms = FormTemplate::categoryUid($extended->uid)->count();
$this->assertSame(4, $totalForms,
"Expected exactly 4 canonical Popup form templates (Top/Bottom/Left/Right Banner), got {$totalForms}. " .
'If this fails, Stage09 demo seeder may be re-introducing fake "Demo Form Template N" entries.');
$this->assertSame(4, $baseForms,
"Expected all 4 form templates to be classified as Base, got {$baseForms} Base. " .
'Per user spec, form templates start as Base — admins create Extended ones via the picker.');
$this->assertSame(0, $extForms,
"Expected 0 Extended form templates after a fresh reset, got {$extForms} Extended");
}
public function test_template_reset_seeds_canonical_form_template_names(): void
{
Artisan::call('template:reset');
$names = FormTemplate::orderBy('id')->pluck('name')->all();
// The 4th-position form template name has a typo in the source ("Popup Form Banner"
// with extra spaces) — this is preserved intentionally so the test reflects reality.
$this->assertContains('Popup Form Right Banner', $names);
$this->assertContains('Popup Form Top Banner', $names);
$this->assertContains('Popup Form Bottom Banner', $names);
$this->assertCount(4, $names, 'Exactly 4 form templates expected');
}
public function test_template_reset_seeds_system_templates(): void
{
Artisan::call('template:reset');
$count = SystemTemplate::count();
// The exact count depends on the templateMetas array in TemplateSeeder::resetSystemTemplates()
// — currently 19 (sender_verification_email, sign_up_form, sign_up_confirmation_*,
// unsubscribe_*, profile_update_*, default_campaign_api_template, pricing_page, ...).
// Use a lower bound so adding new system templates doesn't break this test.
$this->assertGreaterThanOrEqual(15, $count,
"Expected at least 15 system templates, got {$count}");
}
public function test_template_reset_is_idempotent(): void
{
// First run
Artisan::call('template:reset');
$emailsAfter1 = SystemEmailTemplate::count();
$formsAfter1 = FormTemplate::count();
$catsAfter1 = TemplateCategory::count();
// Second run — should produce identical counts (reset semantics)
Artisan::call('template:reset');
$emailsAfter2 = SystemEmailTemplate::count();
$formsAfter2 = FormTemplate::count();
$catsAfter2 = TemplateCategory::count();
$this->assertSame($emailsAfter1, $emailsAfter2,
"SystemEmailTemplate count drifted across reset runs ({$emailsAfter1} → {$emailsAfter2}); seeder is not idempotent");
$this->assertSame($formsAfter1, $formsAfter2,
"FormTemplate count drifted across reset runs ({$formsAfter1} → {$formsAfter2})");
$this->assertSame($catsAfter1, $catsAfter2,
"TemplateCategory count drifted across reset runs ({$catsAfter1} → {$catsAfter2})");
}
public function test_template_reset_does_not_re_introduce_legacy_categories(): void
{
Artisan::call('template:reset');
// Belt-and-suspenders: even if the test above passes, also explicitly check
// that the legacy names are absent. A future seeder edit that adds Basic
// alongside Base must fail this test.
$legacyNames = ['Basic', 'Featured', 'Popup'];
foreach ($legacyNames as $legacy) {
$exists = TemplateCategory::whereName($legacy)->exists();
$this->assertFalse($exists,
"Legacy TemplateCategory '{$legacy}' must NOT exist under the unified taxonomy. " .
'See docs/automated/features/admin-templates-overhaul.md');
}
}
public function test_every_system_email_template_has_a_valid_template_relation(): void
{
Artisan::call('template:reset');
$orphans = SystemEmailTemplate::with('template')
->get()
->filter(fn ($t) => $t->template === null)
->count();
$this->assertSame(0, $orphans,
"Found {$orphans} SystemEmailTemplate rows with NULL template relation. " .
'Every wrapper must point to a real Template row.');
}
public function test_every_form_template_has_a_valid_template_relation(): void
{
Artisan::call('template:reset');
$orphans = FormTemplate::with('template')
->get()
->filter(fn ($t) => $t->template === null)
->count();
$this->assertSame(0, $orphans,
"Found {$orphans} FormTemplate rows with NULL template relation");
}
}