feat(form-builder): form_field_configs relational table + non-validation key split + drop validation_rules JSON columns

This commit is contained in:
2026-04-24 22:42:35 +02:00
parent 9d2758a42c
commit d494478c08
31 changed files with 1233 additions and 60 deletions

View File

@@ -34,7 +34,11 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
// Roll back: backfill + create-table. Brings us to a state where
// form_fields.validation_rules exists but form_field_validation_rules
// table does not.
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
// Roll back all WS-5b migrations to reach the pre-WS-5b state
// (validation_rules JSON column present; no relational tables for
// WS-5b). Step count: drop-cols + configs-backfill + create-configs
// + validation-rules-backfill + create-validation-rules = 5.
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
$this->assertFalse(Schema::hasTable('form_field_validation_rules'));
$this->assertTrue(Schema::hasColumn('form_fields', 'validation_rules'));
@@ -91,7 +95,11 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
public function test_tag_categories_and_storage_disk_skipped_for_commit_5(): void
{
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
// Roll back all WS-5b migrations to reach the pre-WS-5b state
// (validation_rules JSON column present; no relational tables for
// WS-5b). Step count: drop-cols + configs-backfill + create-configs
// + validation-rules-backfill + create-validation-rules = 5.
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
$fieldId = $this->seedFieldWithJson([
'field_type' => 'TAG_PICKER',
@@ -111,7 +119,11 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
public function test_required_and_unique_skipped_with_warn(): void
{
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
// Roll back all WS-5b migrations to reach the pre-WS-5b state
// (validation_rules JSON column present; no relational tables for
// WS-5b). Step count: drop-cols + configs-backfill + create-configs
// + validation-rules-backfill + create-validation-rules = 5.
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
$fieldId = $this->seedFieldWithJson([
'field_type' => 'TEXT',
@@ -134,7 +146,11 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
public function test_unknown_top_level_key_fails_migration(): void
{
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
// Roll back all WS-5b migrations to reach the pre-WS-5b state
// (validation_rules JSON column present; no relational tables for
// WS-5b). Step count: drop-cols + configs-backfill + create-configs
// + validation-rules-backfill + create-validation-rules = 5.
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
$this->seedFieldWithJson([
'field_type' => 'TEXT',
@@ -147,7 +163,11 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
public function test_unmapped_field_type_for_min_max_fails_migration(): void
{
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
// Roll back all WS-5b migrations to reach the pre-WS-5b state
// (validation_rules JSON column present; no relational tables for
// WS-5b). Step count: drop-cols + configs-backfill + create-configs
// + validation-rules-backfill + create-validation-rules = 5.
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
$this->seedFieldWithJson([
'field_type' => 'BOOLEAN',
@@ -158,22 +178,34 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
$this->artisan('migrate');
}
public function test_rollback_reconstructs_canonical_json_on_source_tables(): void
public function test_full_wsb_rollback_reconstructs_source_state(): void
{
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
// Architect contract: "the forward+back pair is safe when run as a
// unit; a partial 'rollback this migration but not its create-table
// sibling' state is not supported." This test exercises the
// full-back-then-full-forward cycle — rolling back all WS-5b
// migrations restores the pre-WS-5b state (columns present on
// source tables; validation rules relational table gone).
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
[$numberId] = $this->seedFields();
$this->artisan('migrate')->assertSuccessful();
// Post-migration: rows exist, column dropped.
$this->assertSame(
2,
DB::table('form_field_validation_rules')
->where('owner_id', $numberId)
->count(),
);
$this->assertFalse(Schema::hasColumn('form_fields', 'validation_rules'));
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
// Only the backfill rolled back — the create-table migration still
// applied, so rows remain accessible (until we step back once more).
$this->assertTrue(Schema::hasTable('form_field_validation_rules'));
// Roll back WS-5b fully → column reappears and carries canonical JSON
// reconstructed from the relational rows.
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
$this->assertTrue(Schema::hasColumn('form_fields', 'validation_rules'));
$field = DB::table('form_fields')->where('id', $numberId)->first();
$decoded = json_decode((string) $field->validation_rules, true);
// Rollback reconstructs using canonical keys — the legacy `min`/`max`
// are intentionally NOT resurrected (post-rename semantic).
$this->assertSame(16, $decoded['min_value']);
$this->assertSame(99, $decoded['max_value']);
}