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_as_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->assertTrue($template->isMarketing(), $item['name']); $this->assertStringContainsString('class="Footer"', $template->message, $item['name']); } } 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']); } }