Files
portal-api/tests/Feature/TemplateSeederTest.php
baodev0andClaude Sonnet 5.5 55a834a2f8 feat: nhắc gia hạn QR theo dịch vụ, failover server hosting, CCCD admin, log cấp phát dịch vụ
- Mail campaign: mẫu nhắc gia hạn thanh toán QR, chọn dịch vụ + chu kỳ từng dòng (renewal_services)
- Hosting: nhiều server Plesk cho một gói (service_plan_servers), chuyển server khi server chết
- Admin xem/sửa giấy tờ định danh khách, tuỳ chọn gia hạn cho khách, nhật ký cấp phát dịch vụ
- Đánh dấu mẫu email dịch vụ là non-marketing

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 18:36:41 +07:00

166 lines
8.0 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Customer;
use App\Models\Template;
use App\Services\Email\EmailTemplateRenderer;
use App\Services\Email\EmailTemplateService;
use App\Support\EmailScenarioRegistry;
use App\Support\MailGlobals;
use App\Support\MailPlaceholders;
use Database\Seeders\EmailTemplates\TemplateVariables;
use Database\Seeders\EmailTemplates\ServiceLifecycleTemplates;
use Database\Seeders\TemplateSeeder;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class TemplateSeederTest extends TestCase
{
use DatabaseTransactions;
public function test_seeder_covers_every_registered_scenario_with_required_variables(): void
{
$templates = collect(TemplateSeeder::templates())->keyBy('scenario');
$this->assertEqualsCanonicalizing(array_keys(EmailScenarioRegistry::SCENARIOS), $templates->keys()->all());
foreach (EmailScenarioRegistry::SCENARIOS as $scenario => $definition) {
$item = $templates[$scenario];
$this->assertSame([], EmailScenarioRegistry::missingVariables($definition['required_variables'], $item['subject'], $item['message']), $scenario);
$this->assertStringContainsString('class="NoiDungKM"', $item['message'], $scenario);
$this->assertStringContainsString('class="Footer"', $item['message'], $scenario);
}
}
public function test_legacy_templates_use_the_shared_shell(): void
{
foreach (TemplateSeeder::legacyTemplates() as $item) {
$this->assertNull(EmailScenarioRegistry::find($item['scenario']), $item['scenario']);
$this->assertStringContainsString('class="Header"', $item['message'], $item['scenario']);
$this->assertStringContainsString('class="Footer"', $item['message'], $item['scenario']);
}
}
public function test_service_lifecycle_templates_are_seeded_once_and_are_not_marketing(): void
{
$items = ServiceLifecycleTemplates::all();
$this->assertCount(39, $items);
$this->seed(TemplateSeeder::class);
$this->seed(TemplateSeeder::class);
foreach ($items as $item) {
$this->assertSame(1, Template::where('name', $item['name'])->count(), $item['name']);
$template = Template::where('name', $item['name'])->first();
$this->assertFalse($template->isMarketing(), $item['name']);
$this->assertStringContainsString('class="Footer"', $template->message, $item['name']);
}
}
public function test_only_promotional_templates_are_marketing_and_respect_customer_opt_out(): void
{
$this->seed(TemplateSeeder::class);
$marketing = Template::whereNull('key')->whereNull('scenario')->get()
->filter(fn (Template $template) => $template->isMarketing())
->pluck('name')->sort()->values()->all();
$this->assertSame(['Khuyến mãi tháng 10', 'Ra mắt Premium Hosting'], $marketing);
$this->assertFalse(Template::where('name', 'Nhắc gia hạn dịch vụ - Thanh toán QR')->firstOrFail()->isMarketing());
}
public function test_generic_new_service_template_fills_customer_email_and_hides_empty_access_rows(): void
{
$this->seed(TemplateSeeder::class);
$template = Template::where('name', 'Dịch vụ — Cấp mới dịch vụ (chưa có tài khoản)')->firstOrFail();
$customer = new Customer(['fullname' => 'Nguyễn Văn A', 'email' => 'nva@example.com']);
$values = collect($template->variables())->map(fn ($definition) => $definition['example'] ?? '')
->merge(MailGlobals::values())
->merge(MailPlaceholders::recipientValues($customer, $customer->email))
->merge(['account_pass' => 'Pass#2026'])
->all();
$html = app(EmailTemplateRenderer::class)->render($template, $values)['html'];
$this->assertStringContainsString('nva@example.com', $html);
$this->assertStringContainsString('Pass#2026', $html);
$this->assertStringContainsString(MailGlobals::values()['login_url'], $html);
$this->assertStringNotContainsString('IP / Host', $html);
}
public function test_cloud_vps_new_service_templates_include_service_and_customer_account(): void
{
$this->seed(TemplateSeeder::class);
$customer = new Customer(['fullname' => 'Nguyễn Văn A', 'email' => 'nva@example.com']);
foreach (['đã có tài khoản' => false, 'chưa có tài khoản' => true] as $suffix => $showsPassword) {
$template = Template::where('name', "Cloud VPS — Cấp mới dịch vụ (khách hàng {$suffix})")->firstOrFail();
$values = collect($template->variables())->map(fn ($definition) => $definition['example'] ?? '')
->merge(MailGlobals::values())
->merge(MailPlaceholders::recipientValues($customer, $customer->email))
->merge(['account_pass' => 'Pass#2026', 'service_host' => '203.0.113.99'])
->all();
$html = app(EmailTemplateRenderer::class)->render($template, $values)['html'];
$this->assertStringContainsString('nva@example.com', $html, $suffix);
$this->assertStringContainsString('203.0.113.99', $html, $suffix);
$this->assertStringContainsString('Hệ điều hành', $html, $suffix);
$this->assertSame($showsPassword, str_contains($html, 'Pass#2026'), $suffix);
}
}
public function test_every_service_has_new_service_templates_by_account_state(): void
{
$this->seed(TemplateSeeder::class);
$customer = new Customer(['fullname' => 'Nguyễn Văn A', 'email' => 'nva@example.com']);
foreach (['SSL', 'Tên miền', 'Hosting', 'Cloud VPS'] as $label) {
foreach (['đã có tài khoản', 'chưa có tài khoản'] as $suffix) {
$template = Template::where('name', "{$label} — Cấp mới dịch vụ (khách hàng {$suffix})")->firstOrFail();
$values = collect($template->variables())->map(fn ($definition) => $definition['example'] ?? '')
->merge(MailGlobals::values())
->merge(MailPlaceholders::recipientValues($customer, $customer->email))
->all();
$html = app(EmailTemplateRenderer::class)->render($template, $values)['html'];
$this->assertStringContainsString('nva@example.com', $html, "{$label} {$suffix}");
$this->assertStringContainsString(MailGlobals::values()['login_url'], $html, "{$label} {$suffix}");
$this->assertStringNotContainsString('eKYC', $html, "{$label} {$suffix}");
}
}
}
public function test_every_placeholder_has_a_variable_definition(): void
{
foreach ([...TemplateSeeder::templates(), ...TemplateSeeder::legacyTemplates()] as $item) {
$unknown = array_diff(MailPlaceholders::extract($item['subject'], $item['message']), array_keys(TemplateVariables::DEFINITIONS));
$this->assertSame([], array_values($unknown), $item['scenario']);
}
}
public function test_seeded_templates_render_and_rerun_updates_in_place(): void
{
$this->seed(TemplateSeeder::class);
$countAfterFirstRun = Template::count();
$this->seed(TemplateSeeder::class);
$this->assertSame($countAfterFirstRun, Template::count());
$service = app(EmailTemplateService::class);
$renderer = app(EmailTemplateRenderer::class);
foreach (array_keys(EmailScenarioRegistry::SCENARIOS) as $scenario) {
if ($scenario === 'layout') {
continue;
}
$template = $service->loadTemplate($scenario);
$rendered = $renderer->render($template, []);
$this->assertDoesNotMatchRegularExpression(MailPlaceholders::PATTERN, $rendered['subject'] . $rendered['html'], $scenario);
}
$this->assertSame('html', $service->loadTemplate('invoice_notification')->variables()['pay_button']['type']);
$this->assertSame('string', $service->loadTemplate('manual_deposit_status')->variables()['status_label']['type']);
}
}