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>
85 lines
3.1 KiB
PHP
85 lines
3.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\FormBuilder\Bindings;
|
|
|
|
use App\Models\FormBuilder\FormField;
|
|
use App\Models\FormBuilder\FormFieldBinding;
|
|
use App\Models\FormBuilder\FormFieldLibrary;
|
|
use App\Models\FormBuilder\FormSchema;
|
|
use App\Models\Organisation;
|
|
use App\Models\User;
|
|
use Database\Seeders\RoleSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* External API shape parity: `FormFieldResource::binding` and
|
|
* `FormFieldLibraryResource::default_binding` must match the pre-WS-5a
|
|
* JSON exactly, but source from the relational table via the service.
|
|
*/
|
|
final class FormFieldResourceBindingOutputTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private Organisation $org;
|
|
|
|
private User $admin;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->seed(RoleSeeder::class);
|
|
$this->org = Organisation::factory()->create();
|
|
$this->admin = User::factory()->create();
|
|
$this->org->users()->attach($this->admin, ['role' => 'org_admin']);
|
|
}
|
|
|
|
public function test_form_field_resource_serialises_entity_owned_binding(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
$schema = FormSchema::factory()->create(['organisation_id' => $this->org->id]);
|
|
$field = FormField::factory()->create(['form_schema_id' => $schema->id]);
|
|
FormFieldBinding::factory()->forField($field)->entityOwned('person', 'email')->create();
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$this->org->id}/forms/schemas/{$schema->id}/fields");
|
|
$response->assertOk();
|
|
$payload = collect($response->json('data'))->firstWhere('id', $field->id);
|
|
$this->assertSame([
|
|
'mode' => 'entity_owned',
|
|
'entity' => 'person',
|
|
'column' => 'email',
|
|
], $payload['binding']);
|
|
}
|
|
|
|
public function test_form_field_resource_returns_null_binding_when_none(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
$schema = FormSchema::factory()->create(['organisation_id' => $this->org->id]);
|
|
$field = FormField::factory()->create(['form_schema_id' => $schema->id]);
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$this->org->id}/forms/schemas/{$schema->id}/fields");
|
|
$response->assertOk();
|
|
$payload = collect($response->json('data'))->firstWhere('id', $field->id);
|
|
$this->assertNull($payload['binding']);
|
|
}
|
|
|
|
public function test_form_field_library_resource_serialises_default_binding(): void
|
|
{
|
|
Sanctum::actingAs($this->admin);
|
|
$library = FormFieldLibrary::factory()->create(['organisation_id' => $this->org->id]);
|
|
FormFieldBinding::factory()->forLibrary($library)->mirrored('user_profile', 'bio')->create();
|
|
|
|
$response = $this->getJson("/api/v1/organisations/{$this->org->id}/forms/field-library/{$library->id}");
|
|
$response->assertOk();
|
|
$response->assertJsonPath('data.default_binding', [
|
|
'mode' => 'mirrored',
|
|
'entity' => 'user_profile',
|
|
'column' => 'bio',
|
|
'sync_direction' => 'write_on_submit',
|
|
]);
|
|
}
|
|
}
|