service = app(EmailService::class); $this->organisation = Organisation::factory()->create(['name' => 'Test Org']); } public function test_send_creates_email_log_with_queued_status(): void { Queue::fake(); $log = $this->service->send( type: EmailTemplateType::INVITATION, recipientEmail: 'test@example.com', recipientName: 'Test User', variables: ['organisation_name' => 'Test Org'], organisation: $this->organisation, ); $this->assertDatabaseHas('email_logs', [ 'id' => $log->id, 'recipient_email' => 'test@example.com', 'template_type' => 'invitation', 'status' => 'queued', 'organisation_id' => $this->organisation->id, ]); } public function test_send_dispatches_queued_job(): void { Queue::fake(); $this->service->send( type: EmailTemplateType::INVITATION, recipientEmail: 'test@example.com', recipientName: 'Test User', variables: ['organisation_name' => 'Test Org'], organisation: $this->organisation, ); Queue::assertPushed(SendTransactionalEmail::class, function ($job) { return $job->recipientEmail === 'test@example.com' && $job->type === EmailTemplateType::INVITATION; }); } public function test_resolve_template_returns_defaults_without_org(): void { $template = $this->service->resolveTemplate(EmailTemplateType::INVITATION); $this->assertFalse($template['is_custom']); $this->assertStringContainsString('{organisation_name}', $template['subject']); $this->assertEquals('Uitnodiging accepteren', $template['button_text']); } public function test_resolve_template_returns_org_override_when_exists(): void { OrganisationEmailTemplate::factory()->create([ 'organisation_id' => $this->organisation->id, 'type' => EmailTemplateType::INVITATION->value, 'subject' => 'Aangepaste uitnodiging', 'heading' => 'Welkom!', 'body_text' => 'Aangepaste tekst', 'button_text' => 'Klik hier', ]); $template = $this->service->resolveTemplate(EmailTemplateType::INVITATION, $this->organisation); $this->assertTrue($template['is_custom']); $this->assertEquals('Aangepaste uitnodiging', $template['subject']); $this->assertEquals('Klik hier', $template['button_text']); } public function test_resolve_template_falls_back_to_defaults_without_override(): void { $template = $this->service->resolveTemplate(EmailTemplateType::PASSWORD_RESET, $this->organisation); $this->assertFalse($template['is_custom']); $this->assertEquals('Wachtwoord resetten', $template['subject']); } public function test_variable_substitution_replaces_placeholders(): void { Queue::fake(); $log = $this->service->send( type: EmailTemplateType::INVITATION, recipientEmail: 'test@example.com', recipientName: 'Test User', variables: ['organisation_name' => 'Feestfabriek'], organisation: $this->organisation, ); $this->assertDatabaseHas('email_logs', [ 'id' => $log->id, 'subject' => 'Je bent uitgenodigd voor Feestfabriek', ]); } public function test_branding_uses_org_settings_when_available(): void { OrganisationEmailSettings::factory()->create([ 'organisation_id' => $this->organisation->id, 'primary_color' => '#FF0000', 'secondary_color' => '#00FF00', 'footer_text' => 'Custom Footer', 'reply_to_email' => 'reply@example.com', 'reply_to_name' => 'Reply Name', ]); $branding = $this->service->resolveBranding($this->organisation); $this->assertEquals('#FF0000', $branding['primary_color']); $this->assertEquals('#00FF00', $branding['secondary_color']); $this->assertEquals('Custom Footer', $branding['footer_text']); $this->assertEquals('reply@example.com', $branding['reply_to_email']); $this->assertEquals('Reply Name', $branding['reply_to_name']); } public function test_branding_falls_back_to_defaults(): void { $branding = $this->service->resolveBranding($this->organisation); $this->assertEquals('#6366F1', $branding['primary_color']); $this->assertEquals('#4F46E5', $branding['secondary_color']); $this->assertStringContainsString('Test Org', $branding['footer_text']); $this->assertNull($branding['reply_to_email']); } public function test_branding_returns_system_defaults_without_org(): void { $branding = $this->service->resolveBranding(null); $this->assertEquals('#6366F1', $branding['primary_color']); $this->assertStringContainsString('Crewli', $branding['footer_text']); } public function test_get_all_templates_returns_all_types(): void { $templates = $this->service->getAllTemplates($this->organisation); $this->assertCount(count(EmailTemplateType::cases()), $templates); $types = array_column($templates, 'type'); foreach (EmailTemplateType::cases() as $case) { $this->assertContains($case->value, $types); } } }