feat(form-builder): FormFieldValidationRuleService + legacy backfill + snapshot + library row-copy
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\FormBuilder\ValidationRules;
|
||||
|
||||
use App\Enums\FormBuilder\FormFieldValidationRuleType;
|
||||
use App\Exceptions\FormBuilder\UnknownValidationRuleTypeException;
|
||||
use App\Models\FormBuilder\FormField;
|
||||
use App\Models\FormBuilder\FormFieldLibrary;
|
||||
use App\Models\FormBuilder\FormFieldValidationRule;
|
||||
use App\Models\FormBuilder\FormSchema;
|
||||
use App\Models\Organisation;
|
||||
use App\Services\FormBuilder\FormFieldValidationRuleService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class FormFieldValidationRuleServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private FormFieldValidationRuleService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->service = app(FormFieldValidationRuleService::class);
|
||||
}
|
||||
|
||||
public function test_replace_rules_is_transactional_delete_then_insert(): void
|
||||
{
|
||||
$field = $this->makeField();
|
||||
FormFieldValidationRule::factory()->forField($field)
|
||||
->ofType(FormFieldValidationRuleType::MinLength, ['value' => 1])->create();
|
||||
|
||||
$this->service->replaceRules($field, [
|
||||
['rule_type' => 'min_length', 'parameters' => ['value' => 5]],
|
||||
['rule_type' => 'max_length', 'parameters' => ['value' => 40]],
|
||||
]);
|
||||
|
||||
$rules = $this->service->rulesFor($field);
|
||||
$this->assertCount(2, $rules);
|
||||
$this->assertSame(5, $rules->firstWhere('rule_type', FormFieldValidationRuleType::MinLength)->parameters['value']);
|
||||
$this->assertSame(40, $rules->firstWhere('rule_type', FormFieldValidationRuleType::MaxLength)->parameters['value']);
|
||||
}
|
||||
|
||||
public function test_empty_specs_array_clears_all_rules(): void
|
||||
{
|
||||
$field = $this->makeField();
|
||||
FormFieldValidationRule::factory()->forField($field)->create();
|
||||
FormFieldValidationRule::factory()->forField($field)
|
||||
->ofType(FormFieldValidationRuleType::MaxLength, ['value' => 10])->create();
|
||||
|
||||
$this->service->replaceRules($field, []);
|
||||
|
||||
$this->assertCount(0, $this->service->rulesFor($field));
|
||||
}
|
||||
|
||||
public function test_unknown_rule_type_is_rejected(): void
|
||||
{
|
||||
$field = $this->makeField();
|
||||
|
||||
$this->expectException(UnknownValidationRuleTypeException::class);
|
||||
$this->service->replaceRules($field, [
|
||||
['rule_type' => 'not_a_real_rule', 'parameters' => []],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_bad_parameter_shape_is_rejected(): void
|
||||
{
|
||||
$field = $this->makeField();
|
||||
|
||||
$this->expectException(UnknownValidationRuleTypeException::class);
|
||||
$this->service->replaceRules($field, [
|
||||
['rule_type' => 'min_length', 'parameters' => ['value' => 'not-an-int']],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_allowed_mime_types_requires_array(): void
|
||||
{
|
||||
$field = $this->makeField();
|
||||
|
||||
$this->expectException(UnknownValidationRuleTypeException::class);
|
||||
$this->service->replaceRules($field, [
|
||||
['rule_type' => 'allowed_mime_types', 'parameters' => ['mime_types' => 'not-an-array']],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_callback_rejects_unregistered_key(): void
|
||||
{
|
||||
Config::set('form_builder.validation_callbacks', []);
|
||||
$field = $this->makeField();
|
||||
|
||||
$this->expectException(UnknownValidationRuleTypeException::class);
|
||||
$this->service->replaceRules($field, [
|
||||
['rule_type' => 'callback', 'parameters' => ['key' => 'unregistered_callback']],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_callback_accepts_registered_key(): void
|
||||
{
|
||||
Config::set('form_builder.validation_callbacks', [
|
||||
'kvk_lookup' => \stdClass::class,
|
||||
]);
|
||||
$field = $this->makeField();
|
||||
|
||||
$this->service->replaceRules($field, [
|
||||
['rule_type' => 'callback', 'parameters' => ['key' => 'kvk_lookup']],
|
||||
]);
|
||||
|
||||
$rules = $this->service->rulesFor($field);
|
||||
$this->assertSame('kvk_lookup', $rules->first()->parameters['key']);
|
||||
}
|
||||
|
||||
public function test_replace_emits_field_validation_rules_replaced_activity_log(): void
|
||||
{
|
||||
$field = $this->makeField();
|
||||
|
||||
$this->service->replaceRules($field, [
|
||||
['rule_type' => 'min_length', 'parameters' => ['value' => 3]],
|
||||
]);
|
||||
|
||||
$entry = Activity::query()
|
||||
->where('subject_type', 'form_field')
|
||||
->where('subject_id', $field->id)
|
||||
->where('description', 'field.validation_rules_replaced')
|
||||
->first();
|
||||
$this->assertNotNull($entry);
|
||||
$this->assertSame(1, (int) $entry->properties['count']);
|
||||
}
|
||||
|
||||
public function test_replace_on_library_does_not_emit_field_activity_log(): void
|
||||
{
|
||||
// Convention-match with WS-5a: library-level changes are silent in
|
||||
// activity log; only the FormField subject gets the semantic event.
|
||||
$library = FormFieldLibrary::factory()->create([
|
||||
'organisation_id' => Organisation::factory()->create()->id,
|
||||
]);
|
||||
|
||||
$this->service->replaceRules($library, [
|
||||
['rule_type' => 'min_length', 'parameters' => ['value' => 3]],
|
||||
]);
|
||||
|
||||
$entry = Activity::query()
|
||||
->where('subject_type', 'form_field_library')
|
||||
->where('description', 'field.validation_rules_replaced')
|
||||
->first();
|
||||
$this->assertNull($entry);
|
||||
}
|
||||
|
||||
public function test_copy_rules_clones_every_column(): void
|
||||
{
|
||||
$org = Organisation::factory()->create();
|
||||
$library = FormFieldLibrary::factory()->create(['organisation_id' => $org->id]);
|
||||
FormFieldValidationRule::factory()->forLibrary($library)
|
||||
->ofType(FormFieldValidationRuleType::MinLength, ['value' => 3])->create();
|
||||
FormFieldValidationRule::factory()->forLibrary($library)
|
||||
->ofType(FormFieldValidationRuleType::MaxLength, ['value' => 40])->create();
|
||||
|
||||
$schema = FormSchema::factory()->create(['organisation_id' => $org->id]);
|
||||
$field = FormField::factory()->create(['form_schema_id' => $schema->id]);
|
||||
|
||||
$this->service->copyRules($library, $field);
|
||||
|
||||
$rules = $this->service->rulesFor($field);
|
||||
$this->assertCount(2, $rules);
|
||||
$this->assertSame(3, $rules->firstWhere('rule_type', FormFieldValidationRuleType::MinLength)->parameters['value']);
|
||||
$this->assertSame(40, $rules->firstWhere('rule_type', FormFieldValidationRuleType::MaxLength)->parameters['value']);
|
||||
}
|
||||
|
||||
public function test_to_json_shape_empty_collection_returns_null(): void
|
||||
{
|
||||
$field = $this->makeField();
|
||||
$this->assertNull($this->service->toJsonShape($this->service->rulesFor($field)));
|
||||
}
|
||||
|
||||
public function test_to_json_shape_flattens_known_rule_types(): void
|
||||
{
|
||||
$field = $this->makeField();
|
||||
FormFieldValidationRule::factory()->forField($field)
|
||||
->ofType(FormFieldValidationRuleType::MinLength, ['value' => 5])->create();
|
||||
FormFieldValidationRule::factory()->forField($field)
|
||||
->ofType(FormFieldValidationRuleType::Regex, ['pattern' => '/^x/'])->create();
|
||||
FormFieldValidationRule::factory()->forField($field)
|
||||
->ofType(FormFieldValidationRuleType::AllowedMimeTypes, ['mime_types' => ['image/png']])->create();
|
||||
FormFieldValidationRule::factory()->forField($field)
|
||||
->ofType(FormFieldValidationRuleType::EmailFormat, [])->create();
|
||||
|
||||
$shape = $this->service->toJsonShape($this->service->rulesFor($field));
|
||||
|
||||
$this->assertSame(5, $shape['min_length']);
|
||||
$this->assertSame('/^x/', $shape['regex']);
|
||||
$this->assertSame(['image/png'], $shape['allowed_mime_types']);
|
||||
$this->assertTrue($shape['email_format']);
|
||||
}
|
||||
|
||||
private function makeField(): FormField
|
||||
{
|
||||
$org = Organisation::factory()->create();
|
||||
$schema = FormSchema::factory()->create(['organisation_id' => $org->id]);
|
||||
|
||||
return FormField::factory()->create(['form_schema_id' => $schema->id]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user