- 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.
105 lines
4.2 KiB
PHP
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);
|
|
});
|