Session 2 wrote both 'binding' (singular) and 'bindings' (plural) in form_submissions.schema_snapshot for backward compatibility. With no production data yet and dev seeders re-running every cycle, dual- key state has no upside. Snapshots now write 'bindings' only; all readers updated to match. FormFieldBindingService::snapshotShapesFor() simplified to return only ['bindings' => $all]. Pre-existing SchemaSnapshotEmbedsBindingFromRelationalTableTest updated to assert the applicator shape (with id, merge_strategy, trust_level, is_identity_key) on bindings[0]; new SnapshotOnlyContainsBindingsKeyTest enforces the no-legacy-key contract going forward. FormBuilderDevSeeder template snapshot embeds 'bindings' => [] for form-owned fields (Pattern B) instead of 'binding' => null. Other 'binding' string occurrences in the codebase (FormFieldResource, FormFieldService, request validation rules, BindingConflictResolver internal helper key) are unrelated to snapshot dual-state and remain untouched. Refs: WS-6 session 2 deviation #9 cleanup Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
126 lines
4.6 KiB
PHP
126 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\FormBuilder\Bindings;
|
|
|
|
use App\Enums\FormBuilder\FormFieldBindingMode;
|
|
use App\Enums\FormBuilder\FormFieldType;
|
|
use App\Enums\FormBuilder\FormPurpose;
|
|
use App\Enums\FormBuilder\FormSchemaSnapshotMode;
|
|
use App\Models\FormBuilder\FormField;
|
|
use App\Models\FormBuilder\FormFieldBinding;
|
|
use App\Models\FormBuilder\FormSchema;
|
|
use App\Models\FormBuilder\FormSubmission;
|
|
use App\Models\Organisation;
|
|
use App\Models\User;
|
|
use App\Services\FormBuilder\FormSubmissionService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Asserts the `schema_snapshot` JSON `bindings` (plural) key carries the
|
|
* RFC-WS-6 §3 Q6 applicator shape via
|
|
* FormFieldBindingService::toApplicatorShape. Pre-WS-6 used a legacy
|
|
* `binding` (singular) key with a leaner shape; that key was dropped
|
|
* in WS-6 session 2.5 (no production data; dual-key state had no upside).
|
|
*/
|
|
final class SchemaSnapshotEmbedsBindingFromRelationalTableTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_snapshot_embeds_entity_owned_binding_in_applicator_shape(): void
|
|
{
|
|
$schema = $this->schemaWithSnapshot();
|
|
|
|
$field = FormField::factory()->create([
|
|
'form_schema_id' => $schema->id,
|
|
'field_type' => FormFieldType::EMAIL->value,
|
|
'slug' => 'email',
|
|
'label' => 'E-mail',
|
|
]);
|
|
$binding = FormFieldBinding::factory()->forField($field)->entityOwned('person', 'email')->create();
|
|
|
|
$submission = $this->submitFor($schema);
|
|
$snapshot = $submission->fresh()->schema_snapshot;
|
|
|
|
$this->assertIsArray($snapshot);
|
|
$embedded = collect($snapshot['fields'])->firstWhere('id', $field->id);
|
|
$this->assertNotNull($embedded);
|
|
$this->assertArrayNotHasKey('binding', $embedded);
|
|
$this->assertCount(1, $embedded['bindings']);
|
|
$shape = $embedded['bindings'][0];
|
|
$this->assertSame((string) $binding->id, $shape['id']);
|
|
$this->assertSame('entity_owned', $shape['mode']);
|
|
$this->assertSame('person', $shape['entity']);
|
|
$this->assertSame('email', $shape['column']);
|
|
$this->assertSame('overwrite', $shape['merge_strategy']);
|
|
$this->assertSame(50, $shape['trust_level']);
|
|
$this->assertFalse($shape['is_identity_key']);
|
|
}
|
|
|
|
public function test_snapshot_embeds_mirrored_binding_with_sync_direction(): void
|
|
{
|
|
$schema = $this->schemaWithSnapshot();
|
|
|
|
$field = FormField::factory()->create([
|
|
'form_schema_id' => $schema->id,
|
|
'field_type' => FormFieldType::TEXT->value,
|
|
'slug' => 'noodcontact',
|
|
'label' => 'Noodcontact',
|
|
]);
|
|
FormFieldBinding::factory()->forField($field)->mirrored('user_profile', 'emergency_contact_name')->create();
|
|
|
|
$submission = $this->submitFor($schema);
|
|
$snapshot = $submission->fresh()->schema_snapshot;
|
|
|
|
$embedded = collect($snapshot['fields'])->firstWhere('id', $field->id);
|
|
$this->assertCount(1, $embedded['bindings']);
|
|
$shape = $embedded['bindings'][0];
|
|
$this->assertSame('mirrored', $shape['mode']);
|
|
$this->assertSame('user_profile', $shape['entity']);
|
|
$this->assertSame('emergency_contact_name', $shape['column']);
|
|
$this->assertSame('write_on_submit', $shape['sync_direction']);
|
|
}
|
|
|
|
public function test_snapshot_embeds_empty_bindings_for_form_owned_field(): void
|
|
{
|
|
$schema = $this->schemaWithSnapshot();
|
|
|
|
$field = FormField::factory()->create([
|
|
'form_schema_id' => $schema->id,
|
|
'field_type' => FormFieldType::TEXT->value,
|
|
'slug' => 'motivatie',
|
|
'label' => 'Motivatie',
|
|
]);
|
|
|
|
$submission = $this->submitFor($schema);
|
|
$snapshot = $submission->fresh()->schema_snapshot;
|
|
|
|
$embedded = collect($snapshot['fields'])->firstWhere('id', $field->id);
|
|
$this->assertSame([], $embedded['bindings']);
|
|
$this->assertArrayNotHasKey('binding', $embedded);
|
|
}
|
|
|
|
private function schemaWithSnapshot(): FormSchema
|
|
{
|
|
$org = Organisation::factory()->create();
|
|
|
|
return FormSchema::factory()->create([
|
|
'organisation_id' => $org->id,
|
|
'purpose' => FormPurpose::USER_PROFILE->value,
|
|
'snapshot_mode' => FormSchemaSnapshotMode::ALWAYS->value,
|
|
]);
|
|
}
|
|
|
|
private function submitFor(FormSchema $schema): FormSubmission
|
|
{
|
|
$user = User::factory()->create();
|
|
$service = $this->app->make(FormSubmissionService::class);
|
|
|
|
$submission = $service->createDraft($schema, $user, $user);
|
|
|
|
return $service->submit($submission, $user);
|
|
}
|
|
}
|