refactor(form-builder): pre-publish check reads form_field_bindings; drop binding JSON columns

This commit is contained in:
2026-04-24 20:09:27 +02:00
parent 6933e6d700
commit 61719bf8bf
18 changed files with 375 additions and 97 deletions

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Tests\Feature\FormBuilder\Bindings;
use App\Models\FormBuilder\FormField;
use App\Models\FormBuilder\FormFieldLibrary;
use App\Models\FormBuilder\FormSchema;
use App\Models\Organisation;
use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -15,44 +13,48 @@ use Illuminate\Support\Str;
use Tests\TestCase;
/**
* Seeds pre-migration JSON into `form_fields.binding` and
* `form_field_library.default_binding`, then runs the migration forward
* and back asserting both directions.
* Rolls back both WS-5a migrations (drop-columns + create-table), seeds
* pre-migration JSON into `form_fields.binding` and
* `form_field_library.default_binding`, then runs the migrations forward
* and back asserting:
*
* Forward: rows land in `form_field_bindings` with the correct
* owner_type/owner_id + translated columns. Backward: JSON is
* reconstructed into the source tables, table dropped.
* - forward: rows land in form_field_bindings with the correct
* owner_type/owner_id + translated columns; legacy JSON columns are
* dropped afterwards.
* - backward: the rollback pair genuinely reconstructs the JSON shape
* before dropping the table.
*
* The "roll back both steps" contract is explicitly documented in
* `2026_04_25_100001_drop_binding_json_columns.php`.
*/
final class FormFieldBindingMigrationTest extends TestCase
{
use RefreshDatabase;
private const MIGRATION_PATH = 'database/migrations/2026_04_25_100000_create_form_field_bindings_table.php';
public function test_forward_migration_backfills_rows_from_both_json_sources(): void
public function test_forward_migrations_backfill_rows_from_both_json_sources(): void
{
// Start from a clean slate where form_field_bindings does not exist.
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
// Roll back: drop_binding_json_columns → create_form_field_bindings.
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
$this->assertFalse(Schema::hasTable('form_field_bindings'));
$this->assertTrue(Schema::hasColumn('form_fields', 'binding'));
$this->assertTrue(Schema::hasColumn('form_field_library', 'default_binding'));
[$fieldAId, $fieldCId, $fieldDId] = $this->seedFieldsWithBindingJson();
[$libAId, $libCId] = $this->seedLibraryWithBindingJson();
$this->artisan('migrate', [
'--path' => self::MIGRATION_PATH,
'--realpath' => false,
])->assertSuccessful();
$this->artisan('migrate')->assertSuccessful();
$this->assertTrue(Schema::hasTable('form_field_bindings'));
$this->assertFalse(Schema::hasColumn('form_fields', 'binding'));
$this->assertFalse(Schema::hasColumn('form_field_library', 'default_binding'));
$rows = DB::table('form_field_bindings')->orderBy('owner_type')->orderBy('owner_id')->get();
$rows = DB::table('form_field_bindings')->get();
$this->assertCount(5, $rows, 'Expected 3 field + 2 library rows');
$fieldRowA = DB::table('form_field_bindings')
->where('owner_type', 'form_field')
->where('owner_id', $fieldAId)
->first();
$this->assertNotNull($fieldRowA);
$this->assertSame('person', $fieldRowA->target_entity);
$this->assertSame('email', $fieldRowA->target_attribute);
$this->assertSame('entity_owned', $fieldRowA->mode);
@@ -65,7 +67,6 @@ final class FormFieldBindingMigrationTest extends TestCase
->where('owner_type', 'form_field')
->where('owner_id', $fieldCId)
->first();
$this->assertNotNull($fieldRowC);
$this->assertSame('mirrored', $fieldRowC->mode);
$this->assertSame('write_on_submit', $fieldRowC->sync_direction);
$this->assertSame('user_profile', $fieldRowC->target_entity);
@@ -75,14 +76,12 @@ final class FormFieldBindingMigrationTest extends TestCase
->where('owner_type', 'form_field')
->where('owner_id', $fieldDId)
->first();
$this->assertNotNull($fieldRowD);
$this->assertSame('entity_owned', $fieldRowD->mode);
$libRowA = DB::table('form_field_bindings')
->where('owner_type', 'form_field_library')
->where('owner_id', $libAId)
->first();
$this->assertNotNull($libRowA);
$this->assertSame('person', $libRowA->target_entity);
$this->assertSame('first_name', $libRowA->target_attribute);
$this->assertSame('entity_owned', $libRowA->mode);
@@ -91,46 +90,45 @@ final class FormFieldBindingMigrationTest extends TestCase
->where('owner_type', 'form_field_library')
->where('owner_id', $libCId)
->first();
$this->assertNotNull($libRowC);
$this->assertSame('mirrored', $libRowC->mode);
}
public function test_rollback_reconstructs_json_and_drops_table(): void
{
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
[$fieldAId, , ] = $this->seedFieldsWithBindingJson();
[$libAId, ] = $this->seedLibraryWithBindingJson();
$this->artisan('migrate', [
'--path' => self::MIGRATION_PATH,
'--realpath' => false,
])->assertSuccessful();
$this->artisan('migrate')->assertSuccessful();
// Wipe the source JSON to prove the rollback writes back from rows.
DB::table('form_fields')->where('id', $fieldAId)->update(['binding' => null]);
DB::table('form_field_library')->where('id', $libAId)->update(['default_binding' => null]);
// Fully-forward state: columns gone, rows in form_field_bindings.
$this->assertFalse(Schema::hasColumn('form_fields', 'binding'));
$this->assertSame(5, DB::table('form_field_bindings')->count());
// Step back over drop_binding_json_columns → columns reappear empty.
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
$this->assertTrue(Schema::hasColumn('form_fields', 'binding'));
$this->assertNull(DB::table('form_fields')->where('id', $fieldAId)->value('binding'));
// Step back over create_form_field_bindings → JSON reconstructed.
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
$this->assertFalse(Schema::hasTable('form_field_bindings'));
$field = DB::table('form_fields')->where('id', $fieldAId)->first();
$this->assertNotNull($field->binding);
$json = json_decode((string) $field->binding, true);
$this->assertSame([
'mode' => 'entity_owned',
'entity' => 'person',
'column' => 'email',
], $json);
], json_decode((string) $field->binding, true));
$lib = DB::table('form_field_library')->where('id', $libAId)->first();
$this->assertNotNull($lib->default_binding);
$libJson = json_decode((string) $lib->default_binding, true);
$this->assertSame([
'mode' => 'entity_owned',
'entity' => 'person',
'column' => 'first_name',
], $libJson);
], json_decode((string) $lib->default_binding, true));
}
/** @return array{0:string,1:string,2:string} */