seed(RoleSeeder::class); $this->organisation = Organisation::factory()->create(['name' => 'Test Org']); $this->otherOrganisation = Organisation::factory()->create(); $this->orgAdmin = User::factory()->create(); $this->organisation->users()->attach($this->orgAdmin, ['role' => 'org_admin']); $this->outsider = User::factory()->create(); $this->otherOrganisation->users()->attach($this->outsider, ['role' => 'org_admin']); } // ── Email Settings ── public function test_show_returns_defaults_when_no_settings(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/email-settings"); $response->assertOk(); $response->assertJsonPath('data.primary_color', '#6366F1'); } public function test_show_returns_custom_settings(): void { OrganisationEmailSettings::factory()->create([ 'organisation_id' => $this->organisation->id, 'primary_color' => '#FF0000', ]); Sanctum::actingAs($this->orgAdmin); $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/email-settings"); $response->assertOk(); $response->assertJsonPath('data.primary_color', '#FF0000'); } public function test_update_creates_settings_if_not_exists(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->putJson("/api/v1/organisations/{$this->organisation->id}/email-settings", [ 'primary_color' => '#00FF00', 'footer_text' => 'Custom Footer', ]); $response->assertOk(); $this->assertDatabaseHas('organisation_email_settings', [ 'organisation_id' => $this->organisation->id, 'primary_color' => '#00FF00', 'footer_text' => 'Custom Footer', ]); } public function test_update_modifies_existing_settings(): void { OrganisationEmailSettings::factory()->create([ 'organisation_id' => $this->organisation->id, 'primary_color' => '#FF0000', ]); Sanctum::actingAs($this->orgAdmin); $response = $this->putJson("/api/v1/organisations/{$this->organisation->id}/email-settings", [ 'primary_color' => '#00FF00', ]); $response->assertOk(); $response->assertJsonPath('data.primary_color', '#00FF00'); } public function test_update_validates_hex_color(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->putJson("/api/v1/organisations/{$this->organisation->id}/email-settings", [ 'primary_color' => 'invalid', ]); $response->assertUnprocessable(); $response->assertJsonValidationErrors(['primary_color']); } public function test_settings_denied_for_non_org_admin(): void { Sanctum::actingAs($this->outsider); $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/email-settings"); $response->assertForbidden(); } // ── Email Templates ── public function test_index_returns_all_template_types_with_content(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/email-templates"); $response->assertOk(); $this->assertCount(count(EmailTemplateType::cases()), $response->json('data')); } public function test_show_returns_template_with_defaults(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/email-templates/invitation"); $response->assertOk(); $response->assertJsonPath('data.type', 'invitation'); $response->assertJsonPath('data.is_custom', false); $this->assertArrayHasKey('defaults', $response->json('data')); } public function test_update_creates_custom_override(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->putJson("/api/v1/organisations/{$this->organisation->id}/email-templates/invitation", [ 'subject' => 'Aangepaste uitnodiging', 'body_text' => 'Aangepaste tekst voor de uitnodiging.', ]); $response->assertOk(); $response->assertJsonPath('data.is_custom', true); $response->assertJsonPath('data.subject', 'Aangepaste uitnodiging'); $this->assertDatabaseHas('organisation_email_templates', [ 'organisation_id' => $this->organisation->id, 'type' => 'invitation', 'subject' => 'Aangepaste uitnodiging', ]); } public function test_destroy_resets_to_default(): void { OrganisationEmailTemplate::factory()->create([ 'organisation_id' => $this->organisation->id, 'type' => EmailTemplateType::INVITATION->value, ]); Sanctum::actingAs($this->orgAdmin); $response = $this->deleteJson("/api/v1/organisations/{$this->organisation->id}/email-templates/invitation"); $response->assertOk(); $this->assertDatabaseMissing('organisation_email_templates', [ 'organisation_id' => $this->organisation->id, 'type' => 'invitation', ]); } public function test_preview_returns_rendered_html(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/email-templates/invitation/preview"); $response->assertOk(); $this->assertArrayHasKey('html', $response->json('data')); $this->assertStringContainsString('', $response->json('data.html')); } public function test_send_test_queues_email(): void { Queue::fake(); Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/email-templates/invitation/send-test", [ 'email' => 'test@example.com', ]); $response->assertOk(); $this->assertDatabaseHas('email_logs', [ 'organisation_id' => $this->organisation->id, 'recipient_email' => 'test@example.com', 'template_type' => 'invitation', ]); } public function test_templates_denied_for_non_org_admin(): void { Sanctum::actingAs($this->outsider); $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/email-templates"); $response->assertForbidden(); } public function test_show_returns_404_for_invalid_type(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/email-templates/nonexistent"); $response->assertNotFound(); } }