207 lines
7.9 KiB
PHP
207 lines
7.9 KiB
PHP
<?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]);
|
|
}
|
|
}
|