fix(form-builder): restore FK on form_schemas.default_crowd_type_id (WS-6)
The original session 2.5 migration had to omit this FK due to an SQLite-only "rebuild on FK add" cascade-delete quirk. Now that the test infrastructure has moved to MySQL (Task 1 of this session), the quirk does not apply and the FK is restored to match every other FK in this table. Changes: - New migration `2026_04_28_100000_restore_default_crowd_type_id_foreign_key` adds a FOREIGN KEY (default_crowd_type_id) REFERENCES crowd_types(id) ON DELETE SET NULL. Deleting a CrowdType nulls the column on dependent schemas instead of cascading the schema delete. - Original migration's comment block rewritten — the SQLite-quirk rationale was demonstrably misleading; replaced with a forward-looking pointer to the FK-restore migration. - PersonProvisioner::resolveCrowdTypeId() docblock updated: the runtime failsafe is now defense in depth alongside the DB-level FK + publish guard, not the sole load-bearing check. New test (`DefaultCrowdTypeForeignKeyTest`) exercises both the ON-DELETE-SET-NULL cascade and the existence of the FK in information_schema.REFERENTIAL_CONSTRAINTS — the second assertion would have been impossible on SQLite, which is exactly the point. Migration step counts in 5 backfill tests bumped +1 because the FK- restore migration sits at the top of the migration stack: - FormFieldBindingMigrationTest: 17→18, 15→16 - ConditionalLogicBackfillTest: 6→7 - FormFieldConfigBackfillAndDropTest: 12→13 - FormFieldOptionsBackfillTest: 2→3 - FormFieldValidationRuleBackfillTest: 15→16 All 1388 tests pass on MySQL (1386 prior + 2 new FK tests). Larastan baseline unchanged. Refs: RFC-WS-6.md v1.1 §3 Q9 addendum, WS-6 session 2.5 deviation #1 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,7 +46,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
// Roll back only the backfill migration (latest WS-5d step).
|
||||
// Leaves the form_field_options table in place, JSON columns
|
||||
// present on the source tables, and snapshots in pre-WS-5d shape.
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
$this->assertTrue(Schema::hasTable('form_field_options'));
|
||||
$this->assertTrue(Schema::hasColumn('form_fields', 'options'));
|
||||
|
||||
@@ -130,7 +130,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_rollback_reconstructs_json_columns_and_snapshots(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
[$selectId, $multiId, $libraryId] = $this->seedFieldsAndLibraryWithJson();
|
||||
$submissionId = $this->seedSubmissionWithSnapshot($selectId);
|
||||
|
||||
@@ -143,7 +143,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
// Step back over only the backfill migration → JSON columns repopulate
|
||||
// and snapshots revert to flat-string-array shape.
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
$this->assertSame(0, DB::table('form_field_options')->count());
|
||||
|
||||
$select = DB::table('form_fields')->where('id', $selectId)->first();
|
||||
@@ -162,7 +162,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_when_options_present_on_non_option_field_type(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
$this->seedFieldWithOptions('TAG_PICKER', ['Veiligheid', 'Horeca']);
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
@@ -172,7 +172,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_when_options_contains_non_string_entry(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode([
|
||||
['label' => 'A'],
|
||||
@@ -186,7 +186,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_when_options_is_object_shape(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode([
|
||||
'XS' => 'Extra small',
|
||||
@@ -200,7 +200,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_on_translations_length_mismatch(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode(['XS', 'S', 'M']), json_encode([
|
||||
'de' => ['options' => ['Klein', 'Mittel']], // 2 vs 3
|
||||
]));
|
||||
@@ -212,7 +212,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_on_non_string_translation(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode(['XS', 'S']), json_encode([
|
||||
'de' => ['options' => ['Klein', 42]],
|
||||
]));
|
||||
@@ -224,7 +224,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_on_oversized_translation(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode(['XS']), json_encode([
|
||||
'de' => ['options' => [str_repeat('x', 256)]],
|
||||
]));
|
||||
@@ -236,7 +236,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_when_snapshot_options_present_on_non_option_field_type(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 3])->assertSuccessful();
|
||||
$this->seedTemplateWithSnapshotRaw([
|
||||
'fields' => [[
|
||||
'id' => (string) Str::ulid(),
|
||||
|
||||
Reference in New Issue
Block a user