pp-planer/tests/Feature/ServiceMacroOverrideControllerTest.php
Thorsten Bus 6ce5b6e018 feat(controllers): add macro/label import + global assignment + service override controllers and routes
- MacroImportController + LabelImportController: POST endpoints accepting uploaded .bin files,
  delegating to MacrosImportService / LabelsImportService and returning import stats / warnings as JSON.
  Generic German 422 error if parser rejects the file.
- MacroAssignmentController: index renders Settings Inertia page with assignments / macros / labels /
  collections / last-import metadata. store/update/destroy/reorder for global MacroAssignment rows.
- ServiceMacroOverrideController: store snapshots all matching global MacroAssignments into
  service-specific rows when a service opts to override; destroy removes both override and
  service-specific assignments. storeAssignment / updateAssignment / destroyAssignment manage the
  per-service rows directly.
- routes/web.php: 12 new named routes inside the auth middleware group; reorder route placed before
  {macroAssignment} parameter route to avoid capture conflict.
- Tests: 19 new Pest tests across 4 feature files (54 assertions). Full suite 376 passed.
2026-05-03 23:17:04 +02:00

105 lines
4.2 KiB
PHP

<?php
use App\Models\Macro;
use App\Models\MacroAssignment;
use App\Models\Service;
use App\Models\ServiceMacroAssignment;
use App\Models\ServiceMacroOverride;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('override requires authentication', function () {
$service = Service::factory()->create();
$response = $this->post(route('services.macro-overrides.store', $service), ['part_type' => 'song']);
$response->assertRedirect(route('login'));
});
test('store creates override and snapshots global assignments', function () {
$user = User::factory()->create();
$service = Service::factory()->create();
$macro = Macro::factory()->create();
MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro->id, 'position' => 'all_slides', 'order' => 0]);
MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro->id, 'position' => 'first_slide', 'order' => 1]);
$response = $this->actingAs($user)
->postJson(route('services.macro-overrides.store', $service), ['part_type' => 'song']);
$response->assertStatus(200)->assertJson(['success' => true]);
expect(ServiceMacroOverride::where('service_id', $service->id)->where('part_type', 'song')->exists())->toBeTrue();
expect(ServiceMacroAssignment::where('service_id', $service->id)->count())->toBe(2);
});
test('store does not snapshot assignments from different part_type', function () {
$user = User::factory()->create();
$service = Service::factory()->create();
$macro = Macro::factory()->create();
MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro->id, 'position' => 'all_slides', 'order' => 0]);
MacroAssignment::create(['part_type' => 'sermon', 'macro_id' => $macro->id, 'position' => 'all_slides', 'order' => 0]);
$this->actingAs($user)
->postJson(route('services.macro-overrides.store', $service), ['part_type' => 'song']);
expect(ServiceMacroAssignment::where('service_id', $service->id)->count())->toBe(1);
expect(ServiceMacroAssignment::where('service_id', $service->id)->first()->part_type)->toBe('song');
});
test('destroy removes override and service-specific assignments', function () {
$user = User::factory()->create();
$service = Service::factory()->create();
$macro = Macro::factory()->create();
ServiceMacroOverride::create(['service_id' => $service->id, 'part_type' => 'song']);
ServiceMacroAssignment::create([
'service_id' => $service->id,
'part_type' => 'song',
'macro_id' => $macro->id,
'position' => 'all_slides',
'order' => 0,
]);
$response = $this->actingAs($user)
->deleteJson(route('services.macro-overrides.destroy', $service), ['part_type' => 'song']);
$response->assertStatus(200);
expect(ServiceMacroOverride::where('service_id', $service->id)->count())->toBe(0);
expect(ServiceMacroAssignment::where('service_id', $service->id)->count())->toBe(0);
});
test('storeAssignment creates service-level assignment', function () {
$user = User::factory()->create();
$service = Service::factory()->create();
$macro = Macro::factory()->create();
$response = $this->actingAs($user)
->postJson(route('services.macro-assignments.store', $service), [
'part_type' => 'sermon',
'macro_id' => $macro->id,
'position' => 'last_slide',
'order' => 0,
]);
$response->assertStatus(200)->assertJson(['success' => true]);
expect(ServiceMacroAssignment::count())->toBe(1);
expect(ServiceMacroAssignment::first()->service_id)->toBe($service->id);
});
test('destroyAssignment removes service-level assignment', function () {
$user = User::factory()->create();
$service = Service::factory()->create();
$macro = Macro::factory()->create();
$assignment = ServiceMacroAssignment::create([
'service_id' => $service->id,
'part_type' => 'song',
'macro_id' => $macro->id,
'position' => 'all_slides',
'order' => 0,
]);
$response = $this->actingAs($user)
->deleteJson(route('services.macro-assignments.destroy', [$service, $assignment]));
$response->assertStatus(200);
expect(ServiceMacroAssignment::count())->toBe(0);
});