artisan('migrate:rollback', ['--step' => 1])->assertSuccessful(); $this->assertFalse(Schema::hasTable('form_field_bindings')); [$fieldAId, $fieldCId, $fieldDId] = $this->seedFieldsWithBindingJson(); [$libAId, $libCId] = $this->seedLibraryWithBindingJson(); $this->artisan('migrate', [ '--path' => self::MIGRATION_PATH, '--realpath' => false, ])->assertSuccessful(); $this->assertTrue(Schema::hasTable('form_field_bindings')); $rows = DB::table('form_field_bindings')->orderBy('owner_type')->orderBy('owner_id')->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); $this->assertNull($fieldRowA->sync_direction); $this->assertSame('overwrite', $fieldRowA->merge_strategy); $this->assertSame(50, (int) $fieldRowA->trust_level); $this->assertSame(0, (int) $fieldRowA->is_identity_key); $fieldRowC = DB::table('form_field_bindings') ->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); $this->assertSame('emergency_contact_name', $fieldRowC->target_attribute); $fieldRowD = DB::table('form_field_bindings') ->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); $libRowC = DB::table('form_field_bindings') ->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(); [$fieldAId, , ] = $this->seedFieldsWithBindingJson(); [$libAId, ] = $this->seedLibraryWithBindingJson(); $this->artisan('migrate', [ '--path' => self::MIGRATION_PATH, '--realpath' => false, ])->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]); $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); $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); } /** @return array{0:string,1:string,2:string} */ private function seedFieldsWithBindingJson(): array { $org = Organisation::factory()->create(); $schema = FormSchema::factory()->create(['organisation_id' => $org->id]); $fieldA = (string) Str::ulid(); $fieldC = (string) Str::ulid(); $fieldD = (string) Str::ulid(); DB::table('form_fields')->insert([ [ 'id' => $fieldA, 'form_schema_id' => $schema->id, 'field_type' => 'EMAIL', 'slug' => 'email', 'label' => 'E-mail', 'binding' => json_encode(['mode' => 'entity_owned', 'entity' => 'person', 'column' => 'email']), 'value_storage_hint' => 'indexed', 'sort_order' => 0, 'created_at' => now(), 'updated_at' => now(), ], [ 'id' => $fieldC, 'form_schema_id' => $schema->id, 'field_type' => 'TEXT', 'slug' => 'noodcontact', 'label' => 'Noodcontact', 'binding' => json_encode([ 'mode' => 'mirrored', 'entity' => 'user_profile', 'column' => 'emergency_contact_name', 'sync_direction' => 'write_on_submit', ]), 'value_storage_hint' => 'indexed', 'sort_order' => 1, 'created_at' => now(), 'updated_at' => now(), ], [ 'id' => $fieldD, 'form_schema_id' => $schema->id, 'field_type' => 'TEXT', 'slug' => 'voornaam', 'label' => 'Voornaam', 'binding' => json_encode(['mode' => 'entity_owned', 'entity' => 'person', 'column' => 'first_name']), 'value_storage_hint' => 'indexed', 'sort_order' => 2, 'created_at' => now(), 'updated_at' => now(), ], ]); return [$fieldA, $fieldC, $fieldD]; } /** @return array{0:string,1:string} */ private function seedLibraryWithBindingJson(): array { $org = Organisation::factory()->create(); $libA = (string) Str::ulid(); $libC = (string) Str::ulid(); DB::table('form_field_library')->insert([ [ 'id' => $libA, 'organisation_id' => $org->id, 'name' => 'Voornaam bibliotheek', 'slug' => 'voornaam-lib', 'field_type' => 'TEXT', 'label' => 'Voornaam', 'default_binding' => json_encode(['mode' => 'entity_owned', 'entity' => 'person', 'column' => 'first_name']), 'default_is_required' => false, 'default_is_filterable' => false, 'usage_count' => 0, 'is_system' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now(), ], [ 'id' => $libC, 'organisation_id' => $org->id, 'name' => 'Noodcontact bibliotheek', 'slug' => 'noodcontact-lib', 'field_type' => 'TEXT', 'label' => 'Noodcontact', 'default_binding' => json_encode([ 'mode' => 'mirrored', 'entity' => 'user_profile', 'column' => 'emergency_contact_phone', 'sync_direction' => 'write_on_submit', ]), 'default_is_required' => false, 'default_is_filterable' => false, 'usage_count' => 0, 'is_system' => false, 'is_active' => true, 'created_at' => now(), 'updated_at' => now(), ], ]); return [$libA, $libC]; } }