feat(form-builder): FormFieldBindingService + library-to-field row copy + snapshot writer
WS-5a commit 2 of 4. FormFieldBindingService owns all writes to the relational binding table. Validation against config/form_binding.php entity-column registry lives here (ARCH §6.2). FormFieldService::insertFromLibrary now calls copyBindings instead of hydrating JSON — the Q3 row-copy mandate. Library and field bindings share the same table; insertion is a row-clone operation. Snapshot writer (FormSubmissionService::buildSnapshot) serialises bindings via toJsonShape so schema_snapshot JSON keeps its ARCH §4.6.1 / §6.3 contract. No snapshot format change. API resources source binding output from the relational table via the same serialiser — external shape preserved. Tests: service transactional behaviour, copyBindings preservation, snapshot parity, API resource parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\FormBuilder\Bindings;
|
||||
|
||||
use App\Enums\FormBuilder\FormFieldBindingMode;
|
||||
use App\Models\FormBuilder\FormFieldBinding;
|
||||
use App\Models\FormBuilder\FormFieldLibrary;
|
||||
use App\Models\FormBuilder\FormSchema;
|
||||
use App\Models\Organisation;
|
||||
use App\Services\FormBuilder\FormFieldService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class InsertFromLibraryCopiesBindingsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_insert_from_library_copies_all_bindings_and_increments_usage(): void
|
||||
{
|
||||
$org = Organisation::factory()->create();
|
||||
$schema = FormSchema::factory()->create(['organisation_id' => $org->id]);
|
||||
$library = FormFieldLibrary::factory()->create([
|
||||
'organisation_id' => $org->id,
|
||||
'usage_count' => 0,
|
||||
]);
|
||||
|
||||
FormFieldBinding::factory()->forLibrary($library)->entityOwned('person', 'email')->create();
|
||||
FormFieldBinding::factory()->forLibrary($library)->mirrored('user_profile', 'bio')->create();
|
||||
|
||||
$service = $this->app->make(FormFieldService::class);
|
||||
$field = $service->insertFromLibrary($schema, $library);
|
||||
|
||||
$copied = FormFieldBinding::query()
|
||||
->withoutGlobalScopes()
|
||||
->where('owner_type', 'form_field')
|
||||
->where('owner_id', $field->id)
|
||||
->orderBy('target_attribute')
|
||||
->get();
|
||||
|
||||
$this->assertCount(2, $copied);
|
||||
$this->assertSame(['bio', 'email'], $copied->pluck('target_attribute')->all());
|
||||
$this->assertSame(FormFieldBindingMode::Mirrored, $copied->firstWhere('target_attribute', 'bio')->mode);
|
||||
$this->assertSame(FormFieldBindingMode::EntityOwned, $copied->firstWhere('target_attribute', 'email')->mode);
|
||||
|
||||
$this->assertSame(1, (int) $library->fresh()->usage_count);
|
||||
}
|
||||
|
||||
public function test_insert_from_library_without_bindings_creates_field_without_bindings(): void
|
||||
{
|
||||
$org = Organisation::factory()->create();
|
||||
$schema = FormSchema::factory()->create(['organisation_id' => $org->id]);
|
||||
$library = FormFieldLibrary::factory()->create(['organisation_id' => $org->id]);
|
||||
|
||||
$service = $this->app->make(FormFieldService::class);
|
||||
$field = $service->insertFromLibrary($schema, $library);
|
||||
|
||||
$this->assertSame(0, FormFieldBinding::query()
|
||||
->withoutGlobalScopes()
|
||||
->where('owner_type', 'form_field')
|
||||
->where('owner_id', $field->id)
|
||||
->count(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user